diff --git a/.github/workflows/python_PR.yml b/.github/workflows/python_PR.yml index 33a8a6ee..127ae7a1 100644 --- a/.github/workflows/python_PR.yml +++ b/.github/workflows/python_PR.yml @@ -9,6 +9,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - uses: mamba-org/setup-micromamba@v2 with: micromamba-version: 'latest' @@ -76,11 +78,18 @@ jobs: micromamba create -n build python=${{matrix.python-version}} pip wheel -c conda-forge micromamba activate build - micromamba install 'gudhi>=3.11' 'numpy>=2' pot 'cython>=3' pytest scikit-learn matplotlib joblib tqdm scipy tbb tbb-devel libboost-devel python-build llvm-openmp -c conda-forge + micromamba install 'gudhi>=3.11' 'numpy>=2' pot 'cython>=3' pytest scikit-learn matplotlib joblib tqdm scipy tbb tbb-devel libboost libboost-devel python-build llvm-openmp -c conda-forge pip install pykeops filtration-domination --upgrade micromamba install c-compiler cxx-compiler -c conda-forge + - name: tree + if: ${{ matrix.os != 'windows-latest' }} + shell: bash -el {0} + run: | + micromamba activate build + micromamba install tree -c conda-forge + tree - name: (windows) add dlls if: ${{ matrix.os == 'windows-latest' }} shell: pwsh diff --git a/.gitignore b/.gitignore index 7572b31f..391c4e87 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ multipers/slicer.pxd multipers/simplex_tree_multi.pyx multipers/mma_structures.pyx multipers/filtration_conversions.pxd +multipers/filtrations.pxd ## Hidden files .* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3c63a8d2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "AIDA"] + path = AIDA + url = https://github.com/JanJend/AIDA + branch = hn_computation +[submodule "Persistence-Algebra"] + path = Persistence-Algebra + url = https://github.com/JanJend/Persistence-Algebra + branch = aida-changes diff --git a/AIDA b/AIDA new file mode 160000 index 00000000..9f892f5a --- /dev/null +++ b/AIDA @@ -0,0 +1 @@ +Subproject commit 9f892f5a6140d9577ca576cf6940b0c5e3fffd8a diff --git a/MANIFEST.in b/MANIFEST.in index 4c0f9425..32f20353 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ recursive-include multipers *.py *.so *.tp *.pxd *.h *.pyx *.dll *.dylib *.lib *.pyd +recursive-include AIDA *.hpp *.cpp +recursive-include Persistence-Algebra/include *.hpp include _tempita_grid_gen.py -global-exclude *.cpp +global-exclude multipers/*.cpp diff --git a/Persistence-Algebra b/Persistence-Algebra new file mode 160000 index 00000000..d29a61f6 --- /dev/null +++ b/Persistence-Algebra @@ -0,0 +1 @@ +Subproject commit d29a61f6cc49ae9c62369226702be59b32e67661 diff --git a/_tempita_grid_gen.py b/_tempita_grid_gen.py index ecac6e2e..3526b47f 100644 --- a/_tempita_grid_gen.py +++ b/_tempita_grid_gen.py @@ -1,3 +1,4 @@ +import os import pickle from itertools import product @@ -6,13 +7,13 @@ ## Columns of the matrix backend. # with ordered by their performance on some synthetic benchmarks. columns_name = [ # only one column is necessary - "Available_columns::" + stuff + "multipers::tmp_interface::Available_columns::" + stuff for stuff in ( - "INTRUSIVE_SET", # At least one of these is necessary. This one is the default + "INTRUSIVE_SET", # At least one of these is necessary. This one is the default # "SET", # "HEAP", # "UNORDERED_SET", - "NAIVE_VECTOR", + # "NAIVE_VECTOR", # "VECTOR", # "INTRUSIVE_LIST", # "LIST", @@ -22,11 +23,14 @@ ## Value types : CTYPE, PYTHON_TYPE, short value_types = [ - ("int32_t", "np.int32", "i32"), # necessary - ("int64_t", "np.int64", "i64"), - ("float", "np.float32", "f32"), - ("double", "np.float64", "f64"), # necessary + ("int32_t", "np.int32", "i32"), # necessary + # ("int64_t", "np.int64", "i64"), + # ("float", "np.float32", "f32"), + ("double", "np.float64", "f64"), # necessary ] +COARSENNED_VALUE_TYPE = ("int32_t", "np.int32", "i32") +REAL_VALUE_TYPE = ("double", "np.float64", "f64") + ## True needed for MMA, and False is default value vineyards_values = [ @@ -39,21 +43,42 @@ kcritical_options = [ # True, - False, # necessary + False, # necessary ] ## matrix_types = [ # - "Matrix", # necessary + "Matrix", # necessary # "Graph", # "Clement", - "GudhiCohomology", + # "GudhiCohomology", +] + +filtration_containers = [ + # "Dynamic_multi_parameter_filtration", + # "Degree_rips_bifiltration", + "Multi_parameter_filtration", ] +short_filtration_container = { + "Dynamic_multi_parameter_filtration": "Dynamic", + "Degree_rips_bifiltration": "Flat", + "Multi_parameter_filtration": "Contiguous", +} + + +def get_cfiltration_type(container, dtype, is_kcritical, co=False): + return f"Gudhi::multi_filtration::{container}<{dtype[0]},false,!{str(is_kcritical).lower()}>" + + +def get_python_filtration_type(container, dtype, is_kcritical, co=False): + return f"{'K' if is_kcritical else ''}{short_filtration_container[container]}_{dtype[2]}" # Removes some impossible / unnecessary combinations -def check_combination(backend_type, is_vine, is_kcritical, value_type, column_type): +def check_combination( + backend_type, is_vine, is_kcritical, value_type, column_type, filtration_container +): if backend_type in ["Clement", "Graph"]: if not is_vine: return False @@ -63,23 +88,33 @@ def check_combination(backend_type, is_vine, is_kcritical, value_type, column_ty if backend_type in ["Graph", "GudhiCohomology"]: if column_type[0] != 0: return False + if filtration_container == "flat": + if not is_kcritical: + return False + if value_type[0] == "f": + return False return True -def get_slicer(backend_type, is_vine, is_kcritical, value_type, column_type): +def get_slicer( + backend_type, is_vine, is_kcritical, value_type, column_type, filtration_container +): stuff = locals() ctype, pytype, short_type = value_type col_idx, col = column_type - PYTHON_TYPE = f"_{'K' if is_kcritical else ''}Slicer_{backend_type}{col_idx}{'_vine' if is_vine else ''}_{short_type}" - CTYPE = f"TrucPythonInterface" + PYTHON_TYPE = f"_{'K' if is_kcritical else ''}{short_filtration_container[filtration_container]}Slicer_{backend_type}{col_idx}{'_vine' if is_vine else ''}_{short_type}" + CTYPE = f"multipers::tmp_interface::TrucPythonInterface" IS_SIMPLICIAL = False IS_VINE = is_vine IS_KCRITICAL = is_kcritical - FILTRATION_TYPE = ( - ("Multi_critical_filtration" if is_kcritical else "One_critical_filtration") - + "[" - + ctype - + "]" + # FILTRATION_TYPE = ( + # ("Multi_critical_filtration" if is_kcritical else "One_critical_filtration") + # + "[" + # + ctype + # + "]" + # ) + FILTRATION_TYPE = get_python_filtration_type( + filtration_container, value_type, is_kcritical ) return { "TRUC_TYPE": CTYPE, @@ -90,9 +125,11 @@ def get_slicer(backend_type, is_vine, is_kcritical, value_type, column_type): "IS_KCRITICAL": IS_KCRITICAL, "C_VALUE_TYPE": ctype, "PY_VALUE_TYPE": pytype, - "COLUMN_TYPE": col.split("::")[1], + "COLUMN_TYPE": col.split("::")[3], "SHORT_VALUE_TYPE": short_type, + "SHORT_FILTRATION_TYPE": short_filtration_container[filtration_container], "FILTRATION_TYPE": FILTRATION_TYPE, + "FILTRATION_CONTAINER_STR": filtration_container, "PERS_BACKEND_TYPE": backend_type, "IS_FLOAT": short_type[0] == "f", } @@ -101,12 +138,13 @@ def get_slicer(backend_type, is_vine, is_kcritical, value_type, column_type): # {{for CTYPE_H, CTYPE,PYTHON_TYPE, IS_SIMPLICIAL, IS_VINE, IS_KCRITICAL, C_VALUE_TYPE, PY_VALUE_TYPE, COL, SHORT,FILTRATION_TYPE in slicers}} slicers = [ get_slicer(**kwargs) - for backend_type, is_vine, is_kcritical, value_type, column_type in product( + for backend_type, is_vine, is_kcritical, value_type, column_type, filtration_container in product( matrix_types, vineyards_values, kcritical_options, value_types, enumerate(columns_name), + filtration_containers, ) if check_combination( **( @@ -116,44 +154,127 @@ def get_slicer(backend_type, is_vine, is_kcritical, value_type, column_type): "is_kcritical": is_kcritical, "value_type": value_type, "column_type": column_type, + "filtration_container": filtration_container, } ) ) ] -for D in slicers: - print(D) +os.makedirs("build/tmp", exist_ok=True) + + +print("#----------------------") +print("#----------------------") +print("Value_types") +print("#----------------------") +print("#----------------------") +print(*value_types, sep="\n") +with open("build/tmp/_value_types.pkl", "wb") as f: + pickle.dump(value_types, f) + +print("#----------------------") +print("#----------------------") +print("Filtrations") +print("#----------------------") +print("#----------------------") +Filtrations = [ + { + "python": get_python_filtration_type(F, T, K), + "c": get_cfiltration_type(F, T, K), + "c_value_type": T[0], + "py_value_type": T[1], + "short_value_type": T[2], + "container": F, + "multicritical": K, + } + for F, T, K in product(filtration_containers, value_types, kcritical_options) +] +print(*Filtrations, sep="\n") +with open("build/tmp/_filtration_names.pkl", "wb") as f: + pickle.dump(Filtrations, f) + + +print("#----------------------") +print("#----------------------") +print("Slicers") +print("#----------------------") +print("#----------------------") +print(*slicers, sep="\n") with open("build/tmp/_slicer_names.pkl", "wb") as f: pickle.dump(slicers, f) -## Simplextree -Filtrations_types = [ - ( - ("Multi_critical_filtration", True) - if kcritical - else ("One_critical_filtration", False) +print("#----------------------") +print("#----------------------") +print("SimplexTrees") +print("#----------------------") +print("#----------------------") + + +def get_simplextree_class_name(is_kcritical, value_type, filtration_container): + ctype, pytype, short_type = value_type + python_filtration = get_python_filtration_type( + filtration_container, value_type, is_kcritical ) - for kcritical in kcritical_options -] + python_class_name = ( + "_SimplexTreeMulti_" + + short_filtration_container[filtration_container] + + "_" + + ("K" if is_kcritical else "") + + short_type + ) + return python_class_name -## CTYPE, PYTYPE, SHORT, FILTRATION -to_iter = [ - ( - CTYPE, - PYTYPE, - SHORT, - Filtration + "[" + CTYPE + "]", - is_kcritical, - ("K" if is_kcritical else "") + "F" + SHORT, +def get_simplextree(is_kcritical, value_type, filtration_container): + ctype, pytype, short_type = value_type + IS_KCRITICAL = is_kcritical + python_filtration = get_python_filtration_type( + filtration_container, value_type, is_kcritical ) - for (CTYPE, PYTYPE, SHORT), (Filtration, is_kcritical) in product( - value_types, Filtrations_types + OneKpython_filtration = get_python_filtration_type( + filtration_container, value_type, False ) -] + python_class_name = get_simplextree_class_name( + is_kcritical, value_type, filtration_container + ) + coarsenned_class_name = get_simplextree_class_name( + is_kcritical, COARSENNED_VALUE_TYPE, filtration_container + ) + real_class_name = get_simplextree_class_name( + is_kcritical, REAL_VALUE_TYPE, filtration_container + ) + return { + "IS_KCRITICAL": IS_KCRITICAL, + "CTYPE": ctype, + "PY_VALUE_TYPE": pytype, + "PYTYPE": pytype, + "SHORT_VALUE_TYPE": short_type, + "SHORT_FILTRATION_TYPE": short_filtration_container[filtration_container], + "PyFil": python_filtration, + "CFil": get_cfiltration_type(filtration_container, value_type, is_kcritical), + "FILTRATION_CONTAINER_STR": filtration_container, + "IS_FLOAT": short_type[0] == "f", + "PY_CLASS_NAME": python_class_name, + "COARSENNED_PY_CLASS_NAME": coarsenned_class_name, + "REAL_PY_CLASS_NAME": real_class_name, + "ST_INTERFACE": ( + "Simplex_tree_multi_interface[" + python_filtration + ", " + ctype + "]" + ), + "C2P_Fil": f"{python_filtration}_2_python", + "P2C_Fil": f"python_2_{python_filtration}", + "P2C_1KFil": f"python_2_{OneKpython_filtration}", + "C2P_vFil": f"vect_{python_filtration}_2_python", + "OneCriticalFil": OneKpython_filtration, + } +st_list = [ + get_simplextree(*args) + for args in product(kcritical_options, value_types, filtration_containers) +] +print(*st_list, sep="\n") + with open("build/tmp/_simplextrees_.pkl", "wb") as f: - pickle.dump(to_iter, f) + pickle.dump(st_list, f) diff --git a/multipers/_signed_measure_meta.py b/multipers/_signed_measure_meta.py index 9c44fe6c..72aba5cc 100644 --- a/multipers/_signed_measure_meta.py +++ b/multipers/_signed_measure_meta.py @@ -126,12 +126,14 @@ def signed_measure( if degree is not None or len(degrees) == 0: degrees = list(degrees) + [degree] if None in degrees: - assert ( - len(degrees) == 1 - ), f"Can only compute one invariant at the time. Got {degrees=}, {invariant=}." + assert len(degrees) == 1, ( + f"Can only compute one invariant at the time. Got {degrees=}, {invariant=}." + ) assert invariant is None or not ( "hilbert" in invariant or "rank" in invariant - ), f"Hilbert and Rank cannot compute `None` degree. got {degrees=}, {invariant=}." + ), ( + f"Hilbert and Rank cannot compute `None` degree. got {degrees=}, {invariant=}." + ) invariant = "euler" if clean is None: clean = True if None in degrees else False @@ -147,9 +149,9 @@ def signed_measure( "hook", ] - assert ( - not plot or filtered_complex.num_parameters == 2 - ), f"Can only plot 2d measures. Got {filtered_complex.num_parameters=}." + assert not plot or filtered_complex.num_parameters == 2, ( + f"Can only plot 2d measures. Got {filtered_complex.num_parameters=}." + ) if grid is None: if not filtered_complex.is_squeezed: @@ -177,7 +179,7 @@ def signed_measure( if not filtered_complex.is_squeezed: if verbose: - print("Coarsening complex...", end="",flush=True) + print("Coarsening complex...", end="", flush=True) t0 = time.time() filtered_complex_ = filtered_complex.grid_squeeze(grid) @@ -201,10 +203,9 @@ def signed_measure( print(f"Done. ({time.time() - t0:.3f}s)") num_parameters = filtered_complex.num_parameters - assert num_parameters == len( - grid - ), f"Number of parameter do not coincide. Got (grid) {len(grid)} and (filtered complex) {num_parameters}." - + assert num_parameters == len(grid), ( + f"Number of parameter do not coincide. Got (grid) {len(grid)} and (filtered complex) {num_parameters}." + ) fix_mass_default = mass_default is not None if is_slicer(filtered_complex_): @@ -233,7 +234,11 @@ def signed_measure( print(f"Done. ({time.time() - t0:.3f}s)") elif filtered_complex_.is_minpres: if verbose: - print("Reduced slicer. Retrieving measure from it...", end="",flush=True) + print( + "Reduced slicer. Retrieving measure from it...", + end="", + flush=True, + ) t0 = time.time() sms = [ _signed_measure_from_slicer( @@ -250,7 +255,7 @@ def signed_measure( len(degrees) == 1 and degrees[0] is None ): if verbose: - print("Retrieving measure from slicer...", end="",flush=True) + print("Retrieving measure from slicer...", end="", flush=True) t0 = time.time() sms = _signed_measure_from_slicer( filtered_complex_, @@ -283,9 +288,9 @@ def signed_measure( print("Computing rank invariant...", end="", flush=True) t0 = time.time() - assert ( - num_parameters == 2 - ), "Rank invariant only implemented for 2-parameter modules." + assert num_parameters == 2, ( + "Rank invariant only implemented for 2-parameter modules." + ) assert not coordinate_measure, "Not implemented" from multipers.simplex_tree_multi import _rank_signed_measure as smri @@ -347,7 +352,7 @@ def signed_measure( if clean: if verbose: - print("Cleaning measure...", end="",flush=True) + print("Cleaning measure...", end="", flush=True) t0 = time.time() sms = clean_sms(sms) if verbose: @@ -376,7 +381,11 @@ def signed_measure( if invariant == "hook": if verbose: - print("Converting rank decomposition to hook decomposition...", end="",flush=True) + print( + "Converting rank decomposition to hook decomposition...", + end="", + flush=True, + ) t0 = time.time() from multipers.point_measure import rectangle_to_hook_minimal_signed_barcode diff --git a/multipers/_slicer_meta.py b/multipers/_slicer_meta.py index c936c482..735f4d35 100644 --- a/multipers/_slicer_meta.py +++ b/multipers/_slicer_meta.py @@ -1,11 +1,17 @@ from copy import deepcopy -from typing import Optional +from typing import Optional import numpy as np import multipers.slicer as mps from multipers.simplex_tree_multi import is_simplextree_multi -from multipers.slicer import _column_type, _valid_dtype, _valid_pers_backend, is_slicer +from multipers.slicer import ( + _column_type, + _filtration_container_type, + _valid_dtype, + _valid_pers_backend, + is_slicer, +) ## TODO : maybe optimize this with cython @@ -82,17 +88,23 @@ def _slicer_from_blocks( pers_backend: _valid_pers_backend, vineyard: bool, is_kcritical: bool, - dtype: type, + dtype: _valid_dtype, col: _column_type, + filtration_container: _filtration_container_type, ): boundary, dimensions, multifiltrations = _blocks2boundary_dimension_grades( blocks, inplace=False, is_kcritical=is_kcritical, ) - slicer = mps.get_matrix_slicer(vineyard, is_kcritical, dtype, col, pers_backend)( - boundary, dimensions, multifiltrations - ) + slicer = mps.get_matrix_slicer( + vineyard, + is_kcritical, + dtype, + col, + pers_backend, + filtration_container, + )(boundary, dimensions, multifiltrations) return slicer @@ -105,6 +117,7 @@ def Slicer( kcritical: Optional[bool] = None, column_type: Optional[_column_type] = None, backend: Optional[_valid_pers_backend] = None, + filtration_container: Optional[str] = None, max_dim: Optional[int] = None, return_type_only: bool = False, ) -> mps.Slicer_type: @@ -143,10 +156,18 @@ def Slicer( vineyard = st.is_vine if vineyard is None else vineyard column_type = st.col_type if column_type is None else column_type backend = st.pers_backend if backend is None else backend + filtration_container = ( + st.filtration_container + if filtration_container is None + else filtration_container + ) else: vineyard = False if vineyard is None else vineyard column_type = "INTRUSIVE_SET" if column_type is None else column_type backend = "Matrix" if backend is None else backend + filtration_container = ( + "contiguous" if filtration_container is None else filtration_container + ) _Slicer = mps.get_matrix_slicer( is_vineyard=vineyard, @@ -154,6 +175,7 @@ def Slicer( dtype=dtype, col=column_type, pers_backend=backend, + filtration_container=filtration_container, ) if return_type_only: return _Slicer @@ -191,14 +213,20 @@ def Slicer( slicer = _Slicer()._build_from_scc_file(st) else: if is_simplextree_multi(st): - blocks = st._to_scc() + slicer = _Slicer().build_from_simplex_tree(st) if st.is_squeezed: filtration_grid = st.filtration_grid else: blocks = st - slicer = _slicer_from_blocks( - blocks, backend, vineyard, is_kcritical, dtype, column_type - ) + slicer = _slicer_from_blocks( + blocks, + backend, + vineyard, + is_kcritical, + dtype, + column_type, + filtration_container, + ) if filtration_grid is not None: slicer.filtration_grid = filtration_grid if reduce: diff --git a/multipers/array_api/numpy.py b/multipers/array_api/numpy.py index 12dccb4a..7430c097 100644 --- a/multipers/array_api/numpy.py +++ b/multipers/array_api/numpy.py @@ -32,6 +32,11 @@ def relu(x): return _np.where(x >= 0, x, 0) +def split_with_sizes(arr, sizes): + indices = _np.cumsum(sizes)[:-1] + return _np.split(arr, indices) + + # Test keops _is_keops_available = None diff --git a/multipers/array_api/torch.py b/multipers/array_api/torch.py index 357f081b..14d6d02e 100644 --- a/multipers/array_api/torch.py +++ b/multipers/array_api/torch.py @@ -30,6 +30,10 @@ _is_keops_available = None +def split_with_sizes(arr, sizes): + return arr.split_with_sizes(sizes) + + def check_keops(): global _is_keops_available, LazyTensor if _is_keops_available is not None: diff --git a/multipers/data/__init__.py b/multipers/data/__init__.py index b72bb345..381aa9bd 100644 --- a/multipers/data/__init__.py +++ b/multipers/data/__init__.py @@ -1 +1 @@ -from .synthetic import * \ No newline at end of file +from .synthetic import * diff --git a/multipers/data/synthetic.py b/multipers/data/synthetic.py index 2902af24..d6ec17a7 100644 --- a/multipers/data/synthetic.py +++ b/multipers/data/synthetic.py @@ -8,7 +8,7 @@ def noisy_annulus( r2: float = 2, dim: int = 2, center: np.ndarray | list | None = None, - **kwargs + **kwargs, ) -> np.ndarray: """Generates a noisy annulus dataset. @@ -47,7 +47,7 @@ def noisy_annulus( def three_annulus(num_pts: int = 500, num_outliers: int = 500): q, r = divmod(num_pts, 3) - num_pts_1, num_pts_2, num_pts_3 = q, q + (r > 0), q + (r > 1) + num_pts_1, num_pts_2, num_pts_3 = q, q + (r > 0), q + (r > 1) X = np.block( [ [np.random.uniform(low=-2, high=2, size=(num_outliers, 2))], diff --git a/multipers/distances.py b/multipers/distances.py index 450198cf..0ee0a504 100644 --- a/multipers/distances.py +++ b/multipers/distances.py @@ -46,8 +46,8 @@ def backend_tensor(x): x = backend_concatenate(pts1[pos_indices1], pts2[neg_indices2]) y = backend_concatenate(pts1[neg_indices1], pts2[pos_indices2]) if threshold is not None: - x[x>threshold]=threshold - y[y>threshold]=threshold + x[x > threshold] = threshold + y[y > threshold] = threshold return x, y diff --git a/multipers/filtration_conversions.pxd.tp b/multipers/filtration_conversions.pxd.tp index 6196b7df..eb7c65b6 100644 --- a/multipers/filtration_conversions.pxd.tp +++ b/multipers/filtration_conversions.pxd.tp @@ -12,10 +12,15 @@ value_types = [ ("double", "np.float64", "f64"), ] + +import pickle +with open("build/tmp/_filtration_names.pkl", "rb") as f: + Filtrations=pickle.load(f) + }} # Python to C++ conversions -from multipers.filtrations cimport One_critical_filtration,Multi_critical_filtration +from multipers.filtrations cimport * from libcpp.vector cimport vector from libcpp cimport bool cimport numpy as cnp @@ -23,62 +28,194 @@ import numpy as np from libc.stdint cimport int32_t, int64_t from cython.operator cimport dereference + + + + + {{for CTYPE, PYTYPE, SHORT in value_types}} ###### ------------------- PY TO CPP #### ---------- +cdef inline vector[{{CTYPE}}] _py2p_{{SHORT}}({{CTYPE}}[:] filtration) noexcept nogil: + # TODO: Is there no directer way to convert a T[:] into a vector[T]? + # A memcpy would be much quicker than a python/cython for loop... + # With a continuous memory use, we could also pass the pointers as iterators if we have access to it? + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](len(filtration)) + for i in range(len(filtration)): + f[i] = filtration[i] + return f + +cdef inline vector[{{CTYPE}}] _py2p2_{{SHORT}}({{CTYPE}}[:,:] filtrations) noexcept nogil: + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](filtrations.shape[0] * filtrations.shape[1]) + k = 0 + for i in range(filtrations.shape[0]): + for j in range(filtrations.shape[1]): + f[k] = filtrations[i,j] + k = k + 1 + return f + +cdef inline vector[Point[{{CTYPE}}]] _py2vp_{{SHORT}}({{CTYPE}}[:,:] filtrations) noexcept nogil: + cdef vector[Point[{{CTYPE}}]] out + cdef Point[{{CTYPE}}] f = Point[{{CTYPE}}](filtrations.shape[1]) + out.reserve(filtrations.shape[0]) + for i in range(filtrations.shape[0]): + for j in range(filtrations.shape[1]): + f[j] = filtrations[i,j] + out.emplace_back(f) + return out + cdef inline Multi_critical_filtration[{{CTYPE}}] _py2kc_{{SHORT}}({{CTYPE}}[:,:] filtrations) noexcept nogil: # cdef {{CTYPE}}[:,:] filtrations = np.asarray(filtrations_, dtype={{PYTYPE}}) - cdef Multi_critical_filtration[{{CTYPE}}] out - out.set_num_generators(filtrations.shape[0]) + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](filtrations.shape[0] * filtrations.shape[1]) + cdef int k = 0; for i in range(filtrations.shape[0]): - out[i].resize(filtrations.shape[1]) for j in range(filtrations.shape[1]): - out[i][j] = filtrations[i,j] + f[k] = filtrations[i,j] + k = k + 1 + cdef Multi_critical_filtration[{{CTYPE}}] out = Multi_critical_filtration[{{CTYPE}}](f.begin(), f.end(), filtrations.shape[1]) out.simplify() return out cdef inline One_critical_filtration[{{CTYPE}}] _py21c_{{SHORT}}({{CTYPE}}[:] filtration) noexcept nogil: - # cdef {{CTYPE}}[:] filtration = np.asarray(filtration_, dtype={{PYTYPE}}) - cdef One_critical_filtration[{{CTYPE}}] out = One_critical_filtration[{{CTYPE}}](0) - out.reserve(len(filtration)) - for i in range(len(filtration)): - out.push_back(filtration[i]) + cdef One_critical_filtration[{{CTYPE}}] out = _py2p_{{SHORT}}(filtration) return out cdef inline vector[One_critical_filtration[{{CTYPE}}]] _py2v1c_{{SHORT}}({{CTYPE}}[:,:] filtrations) noexcept nogil: # cdef {{CTYPE}}[:,:] filtrations = np.asarray(filtrations_, dtype={{PYTYPE}}) cdef vector[One_critical_filtration[{{CTYPE}}]] out + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](filtrations.shape[1]) out.reserve(filtrations.shape[0]) for i in range(filtrations.shape[0]): - out.push_back(_py21c_{{SHORT}}(filtrations[i,:])) + for j in range(filtrations.shape[1]): + f[j] = filtrations[i,j] + out.emplace_back(f) return out +cdef inline vector[Multi_critical_filtration[{{CTYPE}}]] _py2vkc_{{SHORT}}({{CTYPE}}[:,:] filtrations) noexcept nogil: + # cdef {{CTYPE}}[:,:] filtrations = np.asarray(filtrations_, dtype={{PYTYPE}}) + cdef vector[Multi_critical_filtration[{{CTYPE}}]] out + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](filtrations.shape[1]) + out.reserve(filtrations.shape[0]) + for i in range(filtrations.shape[0]): + for j in range(filtrations.shape[1]): + f[j] = filtrations[i,j] + out.emplace_back(f) + return out ###### ------------------- CPP to PY +## tailored for Dynamic_multi_parameter_filtration +## testing finite or not is not necessary for Multi_parameter_filtration +## won't work for Degree_rips_filtration ## CYTHON BUG: using tuples here will cause some weird issues. -cdef inline _ff21cview_{{SHORT}}(One_critical_filtration[{{CTYPE}}]* x, bool copy=False, int duplicate=0): +cdef inline _ff21cview_{{SHORT}}(One_critical_filtration[{{CTYPE}}]* x, bool copy=False): cdef Py_ssize_t num_parameters = dereference(x).num_parameters() - if copy and duplicate and not dereference(x).is_finite(): - return np.full(shape=duplicate, fill_value=dereference(x)[0]) - cdef {{CTYPE}}[:] x_view = <{{CTYPE}}[:num_parameters]>(&(dereference(x)[0])) + if not dereference(x).is_finite(): + return np.full(shape=num_parameters, fill_value=dereference(x)(0,0)) + cdef {{CTYPE}}[:] x_view = <{{CTYPE}}[:num_parameters]>(&(dereference(x)(0,0))) return np.array(x_view) if copy else np.asarray(x_view) -cdef inline _ff2kcview_{{SHORT}}(Multi_critical_filtration[{{CTYPE}}]* x, bool copy=False, int duplicate=0): +cdef inline _ff21cview2_{{SHORT}}({{CTYPE}}* x, Py_ssize_t num_parameters, int duplicate, bool copy=False): + if duplicate: + return np.full(shape=duplicate, fill_value=dereference(x)) + cdef {{CTYPE}}[:] x_view = <{{CTYPE}}[:num_parameters]>(x) + return np.array(x_view) if copy else np.asarray(x_view) + +cdef inline _ff2kcview_{{SHORT}}(Multi_critical_filtration[{{CTYPE}}]* x, bool copy=False): cdef Py_ssize_t k = dereference(x).num_generators() - return [_ff21cview_{{SHORT}}(&(dereference(x)[i]), copy=copy, duplicate=duplicate) for i in range(k)] + cdef Py_ssize_t p = dereference(x).num_parameters() + if dereference(x).is_finite(): + duplicate = 0 + else: + duplicate = p + return [_ff21cview2_{{SHORT}}(&(dereference(x)(i,0)), p, duplicate, copy=copy) for i in range(k)] -cdef inline _vff21cview_{{SHORT}}(vector[One_critical_filtration[{{CTYPE}}]]& x, bool copy = False, int duplicate=0): +cdef inline _vff21cview_{{SHORT}}(vector[One_critical_filtration[{{CTYPE}}]]& x, bool copy = False): cdef Py_ssize_t num_stuff = x.size() - return [_ff21cview_{{SHORT}}(&(x[i]), copy=copy, duplicate=duplicate) for i in range(num_stuff)] + return [_ff21cview_{{SHORT}}(&(x[i]), copy=copy) for i in range(num_stuff)] -cdef inline _vff2kcview_{{SHORT}}(vector[Multi_critical_filtration[{{CTYPE}}]]& x, bool copy = False, int duplicate=0): +cdef inline _vff2kcview_{{SHORT}}(vector[Multi_critical_filtration[{{CTYPE}}]]& x, bool copy = False): cdef Py_ssize_t num_stuff = x.size() - return [_ff2kcview_{{SHORT}}(&(x[i]), copy=copy, duplicate=duplicate) for i in range(num_stuff)] + return [_ff2kcview_{{SHORT}}(&(x[i]), copy=copy) for i in range(num_stuff)] {{endfor}} + +{{for f in Filtrations}} + +{{if f["multicritical"] or f["container"] == "Degree_rips_bifiltration"}} +cdef inline {{f['python']}}_2_python({{f['python']}}* x, bool copy=False): + cdef Py_ssize_t k = dereference(x).num_generators() + + {{if f["container"] == "Degree_rips_bifiltration"}} + + cdef int num_parameters = 2 + cdef {{f["c_value_type"]}}[:] data_view = <{{f["c_value_type"]}}[:k]>(&(dereference(x)(0,0))) + return np.concatenate([np.asarray(data_view, dtype={{f["py_value_type"]}})[:,None], np.arange(k, dtype={{f["py_value_type"]}})[:,None]], axis=1) + + {{else}} + + cdef Py_ssize_t p = dereference(x).num_parameters() + if dereference(x).is_finite(): + duplicate = 0 + else: + duplicate = p + return [_ff21cview2_{{f['short_value_type']}}(&(dereference(x)(i,0)), p, duplicate, copy=copy) for i in range(k)] + + {{endif}} + +{{else}} +# Assumes it's contiguous +cdef inline {{f['python']}}_2_python({{f['python']}}* x, bool copy=False): + cdef Py_ssize_t num_parameters = dereference(x).num_parameters() + if not dereference(x).is_finite(): + return np.full(shape=num_parameters, fill_value=dereference(x)(0,0)) + cdef {{f['c_value_type']}}[:] x_view = <{{f['c_value_type']}}[:num_parameters]>(&(dereference(x)(0,0))) + return np.array(x_view) if copy else np.asarray(x_view) +{{endif}} + +cdef inline vect_{{f['python']}}_2_python(vector[{{f['python']}}]& x, bool copy = False): + cdef Py_ssize_t num_stuff = x.size() + return [{{f['python']}}_2_python(&(x[i]), copy=copy) for i in range(num_stuff)] + + +{{if f["multicritical"]}} +cdef inline {{f['python']}} python_2_{{f['python']}}({{f["c_value_type"]}}[:,:] filtrations) noexcept nogil: + cdef vector[{{CTYPE}}] f = vector[{{CTYPE}}](filtrations.shape[0] * filtrations.shape[1]) + cdef int k = 0; + for i in range(filtrations.shape[0]): + for j in range(filtrations.shape[1]): + f[k] = filtrations[i,j] + k = k + 1 + cdef {{f['python']}} out = {{f['python']}}(f.begin(), f.end(), filtrations.shape[1]) + out.simplify() + return out +{{else}} + + +cdef inline {{f['python']}} python_2_{{f['python']}}({{f["c_value_type"]}}[:] filtration) noexcept nogil: + cdef int num_parameters = filtration.shape[0] + cdef {{f["python"]}} out = {{f['python']}}(num_parameters) + cdef {{f['c_value_type']}}* x + for i in range(num_parameters): + x = &out(0,i) + x[0] = filtration[i] + return out +{{endif}} + +cdef inline vector[{{f['python']}}] python_2_vect_{{f['python']}}({{f["c_value_type"]}}[:,:] filtrations) noexcept nogil: + # cdef {{CTYPE}}[:,:] filtrations = np.asarray(filtrations_, dtype={{PYTYPE}}) + cdef vector[{{f['python']}}] out + cdef vector[{{f["c_value_type"]}}] f = vector[{{f["c_value_type"]}}](filtrations.shape[1]) + out.reserve(filtrations.shape[0]) + for i in range(filtrations.shape[0]): + for j in range(filtrations.shape[1]): + f[j] = filtrations[i,j] + out.emplace_back(f) + return out + +{{endfor}} diff --git a/multipers/filtrations.pxd b/multipers/filtrations.pxd deleted file mode 100644 index 65f95b10..00000000 --- a/multipers/filtrations.pxd +++ /dev/null @@ -1,224 +0,0 @@ -from libcpp.utility cimport pair -from libcpp cimport bool -from libcpp.vector cimport vector -from libcpp cimport tuple -from libc.stdint cimport uintptr_t,intptr_t -from cpython cimport Py_buffer - - -cdef extern from "gudhi/One_critical_filtration.h" namespace "Gudhi::multi_filtration": - cdef cppclass One_critical_filtration[T=*]: - ## Copied from cython vector - ctypedef size_t size_type - ctypedef ptrdiff_t difference_type - ctypedef T value_type - - cppclass const_iterator - cppclass iterator: - iterator() except + - iterator(iterator&) except + - value_type& operator*() - iterator operator++() - iterator operator--() - iterator operator++(int) - iterator operator--(int) - iterator operator+(size_type) - iterator operator-(size_type) - difference_type operator-(iterator) - difference_type operator-(const_iterator) - bint operator==(iterator) - bint operator==(const_iterator) - bint operator!=(iterator) - bint operator!=(const_iterator) - bint operator<(iterator) - bint operator<(const_iterator) - bint operator>(iterator) - bint operator>(const_iterator) - bint operator<=(iterator) - bint operator<=(const_iterator) - bint operator>=(iterator) - bint operator>=(const_iterator) - cppclass const_iterator: - const_iterator() except + - const_iterator(iterator&) except + - const_iterator(const_iterator&) except + - operator=(iterator&) except + - const value_type& operator*() - const_iterator operator++() - const_iterator operator--() - const_iterator operator++(int) - const_iterator operator--(int) - const_iterator operator+(size_type) - const_iterator operator-(size_type) - difference_type operator-(iterator) - difference_type operator-(const_iterator) - bint operator==(iterator) - bint operator==(const_iterator) - bint operator!=(iterator) - bint operator!=(const_iterator) - bint operator<(iterator) - bint operator<(const_iterator) - bint operator>(iterator) - bint operator>(const_iterator) - bint operator<=(iterator) - bint operator<=(const_iterator) - bint operator>=(iterator) - bint operator>=(const_iterator) - - cppclass const_reverse_iterator - cppclass reverse_iterator: - reverse_iterator() except + - reverse_iterator(reverse_iterator&) except + - value_type& operator*() - reverse_iterator operator++() - reverse_iterator operator--() - reverse_iterator operator++(int) - reverse_iterator operator--(int) - reverse_iterator operator+(size_type) - reverse_iterator operator-(size_type) - difference_type operator-(iterator) - difference_type operator-(const_iterator) - bint operator==(reverse_iterator) - bint operator==(const_reverse_iterator) - bint operator!=(reverse_iterator) - bint operator!=(const_reverse_iterator) - bint operator<(reverse_iterator) - bint operator<(const_reverse_iterator) - bint operator>(reverse_iterator) - bint operator>(const_reverse_iterator) - bint operator<=(reverse_iterator) - bint operator<=(const_reverse_iterator) - bint operator>=(reverse_iterator) - bint operator>=(const_reverse_iterator) - cppclass const_reverse_iterator: - const_reverse_iterator() except + - const_reverse_iterator(reverse_iterator&) except + - operator=(reverse_iterator&) except + - const value_type& operator*() - const_reverse_iterator operator++() - const_reverse_iterator operator--() - const_reverse_iterator operator++(int) - const_reverse_iterator operator--(int) - const_reverse_iterator operator+(size_type) - const_reverse_iterator operator-(size_type) - difference_type operator-(iterator) - difference_type operator-(const_iterator) - bint operator==(reverse_iterator) - bint operator==(const_reverse_iterator) - bint operator!=(reverse_iterator) - bint operator!=(const_reverse_iterator) - bint operator<(reverse_iterator) - bint operator<(const_reverse_iterator) - bint operator>(reverse_iterator) - bint operator>(const_reverse_iterator) - bint operator<=(reverse_iterator) - bint operator<=(const_reverse_iterator) - bint operator>=(reverse_iterator) - bint operator>=(const_reverse_iterator) - value_type& operator[](size_type) - #vector& operator=(vector&) - void assign(size_type, const value_type&) - void assign[InputIt](InputIt, InputIt) except + - value_type& at(size_type) except + - value_type& back() - iterator begin() - const_iterator const_begin "begin"() - const_iterator cbegin() - size_type capacity() - void clear() nogil - bint empty() nogil - iterator end() - const_iterator const_end "end"() - const_iterator cend() - iterator erase(iterator) - iterator erase(iterator, iterator) - value_type& front() - iterator insert(iterator, const value_type&) except + - iterator insert(iterator, size_type, const value_type&) except + - iterator insert[InputIt](iterator, InputIt, InputIt) except + - size_type max_size() - void pop_back() - void push_back(value_type&) except + nogil - reverse_iterator rbegin() - const_reverse_iterator const_rbegin "rbegin"() - const_reverse_iterator crbegin() - reverse_iterator rend() - const_reverse_iterator const_rend "rend"() - const_reverse_iterator crend() - void reserve(size_type) except + nogil - void resize(size_type) except + nogil - void resize(size_type, value_type&) except + - # size_type size() - size_type num_parameters() nogil - size_type num_generators() nogil - void swap(vector&) - - # C++11 methods - value_type* data() - const value_type* const_data "data"() - void shrink_to_fit() except + - iterator emplace(const_iterator, ...) except + - value_type& emplace_back(...) except + - - ## end of copied from cython vector - - One_critical_filtration() except + nogil - One_critical_filtration(vector[value_type]&) except + nogil - One_critical_filtration(One_critical_filtration&) except + nogil - - One_critical_filtration(int) nogil - One_critical_filtration& operator=(const One_critical_filtration&) except + - @staticmethod - vector[value_type]& vector[value_type]() nogil - - void push_to_least_common_upper_bound(One_critical_filtration[T]&) nogil - void pull_to_greatest_common_lower_bound(One_critical_filtration[T]&) nogil - - bool is_finite() nogil - - -cdef extern from "gudhi/Multi_critical_filtration.h" namespace "Gudhi::multi_filtration": - cdef cppclass Multi_critical_filtration[T=*]: - ctypedef size_t size_type - ctypedef One_critical_filtration[T] filtration_type - Multi_critical_filtration() except + nogil - Multi_critical_filtration(One_critical_filtration[T]) except + - Multi_critical_filtration[T]& operator=(const Multi_critical_filtration[T]&) except + - size_t num_parameters() noexcept nogil - size_t num_generators() noexcept nogil - void add_guaranteed_generator(One_critical_filtration[T]) nogil - void add_generator(One_critical_filtration[T]) nogil - void reserve(size_t) noexcept nogil - void simplify() nogil - void set_num_generators(size_t) nogil - One_critical_filtration[T]& operator[](int) nogil - - void push_to_least_common_upper_bound(One_critical_filtration[T]&) except + nogil - void pull_to_greatest_common_lower_bound(One_critical_filtration[T]&) except + nogil - -cdef extern from "gudhi/Multi_persistence/Box.h" namespace "Gudhi::multi_persistence": - cdef cppclass Box[T=*]: - ctypedef vector[T] corner_type - Box() except + - Box( vector[T]&, vector[T]&) nogil - Box( pair[vector[T], vector[T]]&) nogil - void inflate(T) nogil - const One_critical_filtration[T]& get_lower_corner() nogil - const One_critical_filtration[T]& get_upper_corner() nogil - bool contains(vector[T]&) nogil - pair[One_critical_filtration[T], One_critical_filtration[T]] get_bounding_corners() nogil - -cdef extern from "gudhi/Multi_persistence/Line.h" namespace "Gudhi::multi_persistence": - cdef cppclass Line[T=*]: - ctypedef One_critical_filtration[T] point_type - Line() except + nogil - Line(One_critical_filtration[T]&) except + nogil - Line(One_critical_filtration[T]&, One_critical_filtration[T]&) except + nogil - - - - - -# ------ useful types: -# ctypedef One_critical_filtration[float] Generator -# ctypedef Multi_critical_filtration[float] kcritical diff --git a/multipers/filtrations.pxd.tp b/multipers/filtrations.pxd.tp new file mode 100644 index 00000000..7b820661 --- /dev/null +++ b/multipers/filtrations.pxd.tp @@ -0,0 +1,332 @@ +{{py: +import pickle + +with open("build/tmp/_filtration_names.pkl", "rb") as f: + Filtrations=pickle.load(f) +}} +from libcpp.utility cimport pair +from libcpp cimport bool +from libcpp.vector cimport vector +from libcpp cimport tuple +from libc.stdint cimport uintptr_t,intptr_t +from cpython cimport Py_buffer +from libc.stdint cimport int32_t, int64_t + +cdef extern from "gudhi/Persistence_slices_interface.h" namespace "multipers::tmp_interface": +{{for f in Filtrations}} + cdef cppclass {{f["python"]}} "{{f['c']}}": + {{f["python"]}}() except + nogil + {{f["python"]}}(int) except + nogil + {{f["python"]}}(int, {{f["c_value_type"]}}) except + nogil + {{f["python"]}}(vector[{{f["c_value_type"]}}]&) except + nogil + {{f["python"]}}(vector[{{f["c_value_type"]}}].iterator, vector[{{f["c_value_type"]}}].iterator, int) except + nogil + {{f["python"]}}& operator=(const {{f["python"]}}&) except + nogil + {{f["c_value_type"]}}& operator()(size_t, size_t) nogil + size_t num_parameters() nogil + size_t num_generators() nogil + size_t num_entries() nogil + void set_num_generators(size_t) nogil + @staticmethod + {{f["python"]}} inf(int) + @staticmethod + {{f["python"]}} minus_inf(int) + @staticmethod + {{f["python"]}} nan(int) + bool is_plus_inf() nogil + bool is_minus_inf() nogil + bool is_nan() nogil + bool is_finite() nogil + bool add_generator(vector[{{f["c_value_type"]}}] &x) nogil + bool add_generator(vector[{{f["c_value_type"]}}] x) nogil + void add_guaranteed_generator(vector[{{f["c_value_type"]}}] &x) nogil + void simplify() nogil + void remove_empty_generators(bool) nogil + bool push_to_least_common_upper_bound(vector[{{f["c_value_type"]}}] &, bool) nogil + bool push_to_least_common_upper_bound(const {{f["python"]}} &, bool) nogil + bool pull_to_greatest_common_lower_bound(vector[{{f["c_value_type"]}}] &, bool) nogil +{{endfor}} + +cdef extern from "gudhi/Persistence_slices_interface.h" namespace "multipers::tmp_interface": + cdef cppclass One_critical_filtration[T]: + ## Copied from cython vector + # ctypedef size_t size_type + # ctypedef ptrdiff_t difference_type + # ctypedef T value_type + + One_critical_filtration() except + nogil + One_critical_filtration(int) except + nogil + One_critical_filtration(int, T) except + nogil + One_critical_filtration(vector[T]&) except + nogil + One_critical_filtration(vector[T].iterator, vector[T].iterator, int) except + nogil + One_critical_filtration[T]& operator=(const One_critical_filtration[T]&) except + nogil + T& operator()(size_t, size_t) nogil + size_t num_parameters() nogil + size_t num_generators() nogil + size_t num_entries() nogil + void set_num_generators(size_t) nogil + @staticmethod + One_critical_filtration inf(int) + @staticmethod + One_critical_filtration minus_inf(int) + @staticmethod + One_critical_filtration nan(int) + bool is_plus_inf() nogil + bool is_minus_inf() nogil + bool is_nan() nogil + bool is_finite() nogil + bool add_generator(vector[T] &x) nogil + bool add_generator(vector[T] x) nogil + void add_guaranteed_generator(vector[T] &x) nogil + void simplify() nogil + void remove_empty_generators(bool) nogil + bool push_to_least_common_upper_bound(vector[T] &, bool) nogil + bool push_to_least_common_upper_bound(const One_critical_filtration &, bool) nogil + bool pull_to_greatest_common_lower_bound(vector[T] &, bool) nogil + # bool pull_to_greatest_common_lower_bound(const One_critical_filtration &, bool) nogil + +cdef extern from "gudhi/Persistence_slices_interface.h" namespace "multipers::tmp_interface": + cdef cppclass Multi_critical_filtration[T]: + ## Copied from cython vector + # ctypedef size_t size_type + # ctypedef ptrdiff_t difference_type + # ctypedef T value_type + + Multi_critical_filtration() except + nogil + Multi_critical_filtration(int) except + nogil + Multi_critical_filtration(int, T) except + nogil + Multi_critical_filtration(vector[T]&) except + nogil + Multi_critical_filtration(vector[T].iterator, vector[T].iterator, int) except + nogil + Multi_critical_filtration[T]& operator=(const Multi_critical_filtration[T]&) except + nogil + T& operator()(size_t, size_t) nogil + size_t num_parameters() nogil + size_t num_generators() nogil + size_t num_entries() nogil + void set_num_generators(size_t) nogil + @staticmethod + Multi_critical_filtration inf(int) + @staticmethod + Multi_critical_filtration minus_inf(int) + @staticmethod + Multi_critical_filtration nan(int) + bool is_plus_inf() nogil + bool is_minus_inf() nogil + bool is_nan() nogil + bool is_finite() nogil + bool add_generator(vector[T] &x) nogil + bool add_generator(vector[T] x) nogil + void add_guaranteed_generator(vector[T] &x) nogil + void simplify() nogil + void remove_empty_generators(bool) nogil + # "push_to_least_common_upper_bound>" + bool push_to_least_common_upper_bound(vector[T] &, bool) nogil + bool push_to_least_common_upper_bound(const Multi_critical_filtration[T] &, bool) nogil + bool pull_to_greatest_common_lower_bound(vector[T] &, bool) nogil + # bool pull_to_greatest_common_lower_bound(const Multi_critical_filtration[T] &, bool) nogil + + +cdef extern from "gudhi/Multi_persistence/Point.h" namespace "Gudhi::multi_persistence": + cdef cppclass Point[T]: + ctypedef size_t size_type + ctypedef ptrdiff_t difference_type + ctypedef T value_type + ctypedef T& reference + ctypedef T* pointer + + cppclass const_iterator + cppclass iterator: + iterator() except + + iterator(iterator&) except + + value_type& operator*() + iterator operator++() + iterator operator--() + iterator operator++(int) + iterator operator--(int) + iterator operator+(size_type) + iterator operator-(size_type) + difference_type operator-(iterator) + difference_type operator-(const_iterator) + bint operator==(iterator) + bint operator==(const_iterator) + bint operator!=(iterator) + bint operator!=(const_iterator) + bint operator<(iterator) + bint operator<(const_iterator) + bint operator>(iterator) + bint operator>(const_iterator) + bint operator<=(iterator) + bint operator<=(const_iterator) + bint operator>=(iterator) + bint operator>=(const_iterator) + cppclass const_iterator: + const_iterator() except + + const_iterator(iterator&) except + + const_iterator(const_iterator&) except + + operator=(iterator&) except + + const value_type& operator*() + const_iterator operator++() + const_iterator operator--() + const_iterator operator++(int) + const_iterator operator--(int) + const_iterator operator+(size_type) + const_iterator operator-(size_type) + difference_type operator-(iterator) + difference_type operator-(const_iterator) + bint operator==(iterator) + bint operator==(const_iterator) + bint operator!=(iterator) + bint operator!=(const_iterator) + bint operator<(iterator) + bint operator<(const_iterator) + bint operator>(iterator) + bint operator>(const_iterator) + bint operator<=(iterator) + bint operator<=(const_iterator) + bint operator>=(iterator) + bint operator>=(const_iterator) + + cppclass const_reverse_iterator + cppclass reverse_iterator: + reverse_iterator() except + + reverse_iterator(reverse_iterator&) except + + value_type& operator*() + reverse_iterator operator++() + reverse_iterator operator--() + reverse_iterator operator++(int) + reverse_iterator operator--(int) + reverse_iterator operator+(size_type) + reverse_iterator operator-(size_type) + difference_type operator-(iterator) + difference_type operator-(const_iterator) + bint operator==(reverse_iterator) + bint operator==(const_reverse_iterator) + bint operator!=(reverse_iterator) + bint operator!=(const_reverse_iterator) + bint operator<(reverse_iterator) + bint operator<(const_reverse_iterator) + bint operator>(reverse_iterator) + bint operator>(const_reverse_iterator) + bint operator<=(reverse_iterator) + bint operator<=(const_reverse_iterator) + bint operator>=(reverse_iterator) + bint operator>=(const_reverse_iterator) + cppclass const_reverse_iterator: + const_reverse_iterator() except + + const_reverse_iterator(reverse_iterator&) except + + operator=(reverse_iterator&) except + + const value_type& operator*() + const_reverse_iterator operator++() + const_reverse_iterator operator--() + const_reverse_iterator operator++(int) + const_reverse_iterator operator--(int) + const_reverse_iterator operator+(size_type) + const_reverse_iterator operator-(size_type) + difference_type operator-(iterator) + difference_type operator-(const_iterator) + bint operator==(reverse_iterator) + bint operator==(const_reverse_iterator) + bint operator!=(reverse_iterator) + bint operator!=(const_reverse_iterator) + bint operator<(reverse_iterator) + bint operator<(const_reverse_iterator) + bint operator>(reverse_iterator) + bint operator>(const_reverse_iterator) + bint operator<=(reverse_iterator) + bint operator<=(const_reverse_iterator) + bint operator>=(reverse_iterator) + bint operator>=(const_reverse_iterator) + + Point() except + nogil + Point(size_type) except + nogil + Point(size_type, const T &) except + nogil + Point(const vector[T]&) except + nogil + Point& operator=(Point&) except + nogil + reference at(size_type) except + + reference operator[](size_type) + reference front() + reference back() + pointer data() + const value_type* const_data "data"() + iterator begin() + const_iterator const_begin "begin"() + const_iterator cbegin() + iterator end() + const_iterator const_end "end"() + const_iterator cend() + reverse_iterator rbegin() + const_reverse_iterator const_rbegin "rbegin"() + const_reverse_iterator crbegin() + reverse_iterator rend() + const_reverse_iterator const_rend "rend"() + const_reverse_iterator crend() + size_type size() + + bint operator<(const Point &, const Point &) + bint operator<=(const Point &, const Point &) + bint operator>(const Point &, const Point &) + bint operator>=(const Point &, const Point &) + bint operator==(const Point &, const Point &) + bint operator!=(const Point &, const Point &) + Point operator-(const Point &) + Point operator-(Point, const Point &) + Point operator-(Point, double) + Point operator-(Point, float) + Point operator-(Point, int) + Point operator-(double, Point) + Point operator-(float, Point) + Point operator-(int, Point) + Point operator+(Point, const Point &) + Point operator+(Point, double) + Point operator+(Point, float) + Point operator+(Point, int) + Point operator+(double, Point) + Point operator+(float, Point) + Point operator+(int, Point) + Point operator*(Point, const Point &) + Point operator*(Point, double) + Point operator*(Point, float) + Point operator*(Point, int) + Point operator*(double, Point) + Point operator*(float, Point) + Point operator*(int, Point) + Point operator/(Point, const Point &) + Point operator/(Point, double) + Point operator/(Point, float) + Point operator/(Point, int) + Point operator/(double, Point) + Point operator/(float, Point) + Point operator/(int, Point) + +cdef extern from "gudhi/Multi_persistence/Box.h" namespace "Gudhi::multi_persistence": + cdef cppclass Box[T=*]: + ctypedef Point[T] corner_type + Box() except + + # Box(corner_type&, corner_type&) nogil + # Box(pair[Point[T], Point[T]]&) nogil + Box(vector[T]&, vector[T]&) nogil + Box(pair[vector[T], vector[T]]&) nogil + void inflate(T) nogil + # const corner_type& get_lower_corner() nogil + # const corner_type& get_upper_corner() nogil + const vector[T]& get_lower_corner() nogil + const vector[T]& get_upper_corner() nogil + # bool contains(corner_type&) nogil + bool contains(vector[T]&) nogil + # pair[corner_type, corner_type] get_bounding_corners() nogil + pair[vector[T], vector[T]] get_bounding_corners() nogil + +cdef extern from "gudhi/Multi_persistence/Line.h" namespace "Gudhi::multi_persistence": + cdef cppclass Line[T=*]: + ctypedef Point[T] point_type + Line() except + nogil + # Line(point_type&) except + nogil + # Line(point_type&, point_type&) except + nogil + Line(vector[T]&) except + nogil + Line(vector[T]&, vector[T]&) except + nogil + + + + + +# ------ useful types: +# ctypedef One_critical_filtration[float] Generator +# ctypedef Multi_critical_filtration[float] kcritical diff --git a/multipers/filtrations/__init__.py b/multipers/filtrations/__init__.py index dbf26580..a6d441d5 100644 --- a/multipers/filtrations/__init__.py +++ b/multipers/filtrations/__init__.py @@ -7,6 +7,7 @@ RipsCodensity, RipsLowerstar, ) + __all__ = [ "CoreDelaunay", "Cubical", diff --git a/multipers/filtrations/density.py b/multipers/filtrations/density.py index e8bae32f..86201a1e 100644 --- a/multipers/filtrations/density.py +++ b/multipers/filtrations/density.py @@ -214,13 +214,13 @@ def multivariate_gaussian_kernel(x_i, y_j, covariance_matrix_inverse): def exponential_kernel(x_i, y_j, bandwidth): # 1 / \sigma * exp( norm(x-y, dim=-1)) - exponent = -(((((x_i - y_j) ** 2)).sum(dim=-1) ** 1 / 2) / bandwidth) + exponent = -((((x_i - y_j) ** 2).sum(dim=-1) ** 1 / 2) / bandwidth) kernel = exponent.exp() / bandwidth return kernel def sinc_kernel(x_i, y_j, bandwidth): - norm = ((((x_i - y_j) ** 2)).sum(dim=-1) ** 1 / 2) / bandwidth + norm = (((x_i - y_j) ** 2).sum(dim=-1) ** 1 / 2) / bandwidth sinc = type(x_i).sinc kernel = 2 * sinc(2 * norm) - sinc(norm) return kernel @@ -239,9 +239,7 @@ def _kernel( case "sinc": return sinc_kernel case _: - assert callable( - kernel - ), f""" + assert callable(kernel), f""" -------------------------- Unknown kernel {kernel}. -------------------------- @@ -361,8 +359,6 @@ def score_samples(self, Y, X=None, return_kernel=False): ) - - class DTM: """ Distance To Measure diff --git a/multipers/filtrations/filtrations.py b/multipers/filtrations/filtrations.py index 47a5eefa..4a71334a 100644 --- a/multipers/filtrations/filtrations.py +++ b/multipers/filtrations/filtrations.py @@ -42,9 +42,9 @@ def RipsLowerstar( - function : ArrayLike of shape (num_data, num_parameters -1) - threshold_radius: max edge length of the rips. Defaults at min(max(distance_matrix, axis=1)). """ - assert ( - points is not None or distance_matrix is not None - ), "`points` or `distance_matrix` has to be given." + assert points is not None or distance_matrix is not None, ( + "`points` or `distance_matrix` has to be given." + ) if distance_matrix is None: api = api_from_tensor(points) points = api.astensor(points) @@ -76,6 +76,7 @@ def RipsLowerstar( st.fill_lowerstar(api.asnumpy(function[:, i]), parameter=1 + i) if api.has_grad(D) or api.has_grad(function): from multipers.grids import compute_grid + filtration_values = [D.ravel(), *[f for f in function.T]] grid = compute_grid(filtration_values) st = st.grid_squeeze(grid) @@ -95,9 +96,9 @@ def RipsCodensity( """ Computes the Rips density filtration. """ - assert ( - bandwidth is None or dtm_mass is None - ), "Density estimation is either via kernels or dtm." + assert bandwidth is None or dtm_mass is None, ( + "Density estimation is either via kernels or dtm." + ) if bandwidth is not None: kde = KDE(bandwidth=bandwidth, kernel=kernel, return_log=return_log) f = -kde.fit(points).score_samples(points) @@ -144,9 +145,9 @@ def DelaunayLowerstar( warn("Cannot keep points gradient unless using `flagify=True`.") points = api.astensor(points) function = api.astensor(function).squeeze() - assert ( - function.ndim == 1 - ), "Delaunay Lowerstar is only compatible with 1 additional parameter." + assert function.ndim == 1, ( + "Delaunay Lowerstar is only compatible with 1 additional parameter." + ) slicer = from_function_delaunay( api.asnumpy(points), api.asnumpy(function), @@ -192,9 +193,9 @@ def DelaunayCodensity( """ TODO """ - assert ( - bandwidth is None or dtm_mass is None - ), "Density estimation is either via kernels or dtm." + assert bandwidth is None or dtm_mass is None, ( + "Density estimation is either via kernels or dtm." + ) if bandwidth is not None: kde = KDE(bandwidth=bandwidth, kernel=kernel, return_log=return_log) f = -kde.fit(points).score_samples(points) @@ -283,9 +284,9 @@ def CoreDelaunay( assert len(ks) > 0, "The parameter ks must contain at least one value." assert np.all(ks > 0), "All values in ks must be positive." - assert np.all( - ks <= len(points) - ), "All values in ks must be less than or equal to the number of points in the point cloud." + assert np.all(ks <= len(points)), ( + "All values in ks must be less than or equal to the number of points in the point cloud." + ) assert len(points) > 0, "The point cloud must contain at least one point." assert points.ndim == 2, f"The point cloud must be a 2D array, got {points.ndim}D." assert beta >= 0, f"The parameter beta must be positive, got {beta}." diff --git a/multipers/grids.pyx b/multipers/grids.pyx index b886953a..d45fa642 100644 --- a/multipers/grids.pyx +++ b/multipers/grids.pyx @@ -510,10 +510,11 @@ def evaluate_mod_in_grid(mod, grid, box=None): (birth_sizes, death_sizes), births, deaths = mod.to_flat_idx(grid) births = evaluate_in_grid(births, grid) deaths = evaluate_in_grid(deaths, grid) + api = api_from_tensors(births, deaths) diff_mod = tuple( zip( - births.split_with_sizes(birth_sizes.tolist()), - deaths.split_with_sizes(death_sizes.tolist()), + api.split_with_sizes(births,birth_sizes.tolist()), + api.split_with_sizes(deaths,death_sizes.tolist()), ) ) return diff_mod diff --git a/multipers/gudhi/Persistence_slices_interface.h b/multipers/gudhi/Persistence_slices_interface.h index 8c00055e..1ec86ab3 100644 --- a/multipers/gudhi/Persistence_slices_interface.h +++ b/multipers/gudhi/Persistence_slices_interface.h @@ -1,132 +1,213 @@ #pragma once -#include "mma_interface_h0.h" -#include "mma_interface_matrix.h" -#include "mma_interface_coh.h" -#include // For static_assert -#include "truc.h" -#include -#include -#include +#include +#include + +#include "gudhi/Multi_parameter_filtration.h" +#include "gudhi/multi_simplex_tree_helpers.h" +#include "gudhi/persistence_matrix_options.h" +#include "gudhi/Multi_parameter_filtered_complex.h" +#include "gudhi/Multi_persistence/Persistence_interface_matrix.h" +#include "gudhi/Multi_persistence/Persistence_interface_cohomology.h" +#include "gudhi/Dynamic_multi_parameter_filtration.h" +#include "gudhi/Degree_rips_bifiltration.h" +#include "gudhi/Slicer.h" + +#include "tmp_h0_pers/mma_interface_h0.h" + +namespace multipers::tmp_interface { template using SimplexTreeMultiOptions = Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration; -enum Column_types_strs { LIST, SET, HEAP, VECTOR, NAIVE_VECTOR, UNORDERED_SET, INTRUSIVE_LIST, INTRUSIVE_SET }; +enum Column_types_strs : std::uint8_t { + LIST, + SET, + HEAP, + VECTOR, + NAIVE_VECTOR, + UNORDERED_SET, + INTRUSIVE_LIST, + INTRUSIVE_SET +}; + +enum Filtration_containers_strs : std::uint8_t { + Dynamic_multi_parameter_filtration, + Multi_parameter_filtration, + Degree_rips_bifiltration +}; using Available_columns = Gudhi::persistence_matrix::Column_types; +template +struct Multi_persistence_options : Gudhi::persistence_matrix::Default_options { + using Index = std::uint32_t; + static const bool has_matrix_maximal_dimension_access = false; + static const bool has_column_pairings = true; + static const bool has_vine_update = true; + static const bool can_retrieve_representative_cycles = true; +}; + +template +struct Multi_persistence_Clement_options : Gudhi::persistence_matrix::Default_options { + using Index = std::uint32_t; + static const bool has_matrix_maximal_dimension_access = false; + static const bool has_column_pairings = true; + static const bool has_vine_update = true; + static const bool is_of_boundary_type = false; + static const Gudhi::persistence_matrix::Column_indexation_types column_indexation_type = + Gudhi::persistence_matrix::Column_indexation_types::POSITION; + static const bool can_retrieve_representative_cycles = true; +}; + +template +struct No_vine_multi_persistence_options : Gudhi::persistence_matrix::Default_options { + using Index = std::uint32_t; + static const bool has_matrix_maximal_dimension_access = false; + static const bool has_column_pairings = true; + static const bool has_vine_update = false; +}; + +template +struct fix_presentation_options : Gudhi::persistence_matrix::Default_options { + using Index = std::uint32_t; + static const bool has_row_access = row_access; + static const bool has_map_column_container = false; + static const bool has_removable_columns = false; // WARN : idx will change if map is not true +}; + template -using BackendOptionsWithVine = Gudhi::multiparameter::truc_interface::Multi_persistence_options; +using BackendOptionsWithVine = Multi_persistence_options; template -using BackendOptionsWithoutVine = Gudhi::multiparameter::truc_interface::No_vine_multi_persistence_options; +using BackendOptionsWithoutVine = No_vine_multi_persistence_options; template -using ClementBackendOptionsWithVine = Gudhi::multiparameter::truc_interface::Multi_persistence_Clement_options; +using ClementBackendOptionsWithVine = Multi_persistence_Clement_options; -using SimplicialStructure = Gudhi::multiparameter::truc_interface::SimplicialStructure; -using PresentationStructure = Gudhi::multiparameter::truc_interface::PresentationStructure; +// using SimplicialStructure = Gudhi::multiparameter::truc_interface::SimplicialStructure; +template +using StructureStuff = Gudhi::multi_persistence::Multi_parameter_filtered_complex; -template -using MatrixBackendNoVine = - Gudhi::multiparameter::truc_interface::Persistence_backend_matrix, Structure>; +template +using MatrixBackendNoVine = Gudhi::multi_persistence::Persistence_interface_matrix>; -template -using MatrixBackendVine = - Gudhi::multiparameter::truc_interface::Persistence_backend_matrix, Structure>; +template +using MatrixBackendVine = Gudhi::multi_persistence::Persistence_interface_matrix>; -template +template using ClementMatrixBackendVine = - Gudhi::multiparameter::truc_interface::Persistence_backend_matrix, Structure>; -using GraphBackendVine = Gudhi::multiparameter::truc_interface::Persistence_backend_h0; + Gudhi::multi_persistence::Persistence_interface_matrix>; +template +using GraphBackendVine = Gudhi::multiparameter::truc_interface::Persistence_backend_h0>; -using Filtration_value = Gudhi::multi_filtration::One_critical_filtration; +template +using Filtration_value = Gudhi::multi_filtration::Multi_parameter_filtration; template -using SimplicialNoVineMatrixTruc = - Gudhi::multiparameter::truc_interface::Truc, SimplicialStructure, Filtration_value>; +using SimplicialNoVineMatrixTruc = Gudhi::multi_persistence::Slicer, MatrixBackendNoVine>; template -using GeneralVineTruc = Gudhi::multiparameter::truc_interface:: - Truc, PresentationStructure, Filtration_value>; +using GeneralVineTruc = Gudhi::multi_persistence::Slicer, MatrixBackendVine>; template -using GeneralNoVineTruc = Gudhi::multiparameter::truc_interface:: - Truc, PresentationStructure, Filtration_value>; +using GeneralNoVineTruc = Gudhi::multi_persistence::Slicer, MatrixBackendNoVine>; template -using GeneralVineClementTruc = Gudhi::multiparameter::truc_interface:: - Truc, PresentationStructure, Filtration_value>; +using GeneralVineClementTruc = Gudhi::multi_persistence::Slicer, ClementMatrixBackendVine>; template -using SimplicialVineMatrixTruc = - Gudhi::multiparameter::truc_interface::Truc, SimplicialStructure, Filtration_value>; +using SimplicialVineMatrixTruc = Gudhi::multi_persistence::Slicer, MatrixBackendVine>; using SimplicialVineGraphTruc = - Gudhi::multiparameter::truc_interface::Truc; + Gudhi::multi_persistence::Slicer, GraphBackendVine>>; + +// multi-critical +template +using Multi_critical_filtration_value = Gudhi::multi_filtration::Multi_parameter_filtration; -// multicrititcal -using Multi_critical_filtrationValue = Gudhi::multi_filtration::Multi_critical_filtration; template -using KCriticalVineTruc = Gudhi::multiparameter::truc_interface:: - Truc, PresentationStructure, Multi_critical_filtrationValue>; +using KCriticalVineTruc = Gudhi::multi_persistence::Slicer, MatrixBackendVine>; template -using Matrix_interface = std::conditional_t, - MatrixBackendNoVine>; +using Matrix_interface = std::conditional_t, MatrixBackendNoVine>; -template -using filtration_options = std::conditional_t, - Gudhi::multi_filtration::One_critical_filtration>; +template +using filtration_options = std::conditional_t, + std::conditional_t, + Gudhi::multi_filtration::Degree_rips_bifiltration>>; template -using MatrixTrucPythonInterface = Gudhi::multiparameter::truc_interface:: - Truc, PresentationStructure, filtration_options>; + Available_columns col = Available_columns::INTRUSIVE_SET, + Filtration_containers_strs filt_cont = Filtration_containers_strs::Multi_parameter_filtration> +using MatrixTrucPythonInterface = + Gudhi::multi_persistence::Slicer, Matrix_interface>; -enum class BackendsEnum { Matrix, Graph, Clement, GudhiCohomology }; +enum class BackendsEnum : std::uint8_t { Matrix, Graph, Clement, GudhiCohomology }; // Create a template metafunction to simplify the type selection -template +template struct PersBackendOptsImpl; -template -struct PersBackendOptsImpl { +template +struct PersBackendOptsImpl { using type = Matrix_interface; }; -template -struct PersBackendOptsImpl { +template +struct PersBackendOptsImpl { static_assert(is_vine, "Clement is vine"); - using type = ClementMatrixBackendVine; + using type = ClementMatrixBackendVine; }; -template -struct PersBackendOptsImpl { +template +struct PersBackendOptsImpl { static_assert(!is_vine, "Gudhi is not vine"); - using type = Gudhi::multiparameter::truc_interface::Persistence_backend_cohomology; + using type = Gudhi::multi_persistence::Persistence_interface_cohomology; }; -template -struct PersBackendOptsImpl { +template +struct PersBackendOptsImpl { static_assert(is_vine, "Graph backend requires is_vine to be true"); - using type = GraphBackendVine; + using type = GraphBackendVine; }; // Helper alias to extract the type -template -using PersBackendOpts = typename PersBackendOptsImpl::type; - -template -using StructureStuff = std::conditional_t; +template +using PersBackendOpts = typename PersBackendOptsImpl::type; template -using TrucPythonInterface = Gudhi::multiparameter::truc_interface:: - Truc, StructureStuff, filtration_options>; + Available_columns col = Available_columns::INTRUSIVE_SET, + Filtration_containers_strs filt_cont = Filtration_containers_strs::Multi_parameter_filtration> +using TrucPythonInterface = Gudhi::multi_persistence::Slicer< + filtration_options, + PersBackendOpts>>; + +//for python +template +using One_critical_filtration = Gudhi::multi_filtration::Multi_parameter_filtration; +template +using Multi_critical_filtration = Gudhi::multi_filtration::Multi_parameter_filtration; + +template +using Bar = std::array; +template +using Barcode = typename Slicer::template Flat_barcode; +template +using Dim_barcode = typename Slicer::template Multi_dimensional_flat_barcode; + +template +std::string slicer_to_str(Slicer& s) +{ + std::stringstream stream; + stream << s; + return stream.str(); +} + +} // namespace multipers::tmp_interface diff --git a/multipers/gudhi/Simplex_tree_interface.h b/multipers/gudhi/Simplex_tree_interface.h index 1a01813c..516a2d65 100644 --- a/multipers/gudhi/Simplex_tree_interface.h +++ b/multipers/gudhi/Simplex_tree_interface.h @@ -11,16 +11,17 @@ #ifndef INCLUDE_SIMPLEX_TREE_INTERFACE_H_ #define INCLUDE_SIMPLEX_TREE_INTERFACE_H_ +#include +#include +#include // std::pair +#include + #include #include #include #include #include -#include -#include // std::pair -#include - namespace Gudhi { /** Model of SimplexTreeOptions. @@ -36,13 +37,14 @@ struct Simplex_tree_options_for_python { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = false; }; -template -class Simplex_tree_interface : public Simplex_tree { +// CHANGED FROM THE ORIGINAL: added template parameter +template +class Simplex_tree_interface : public Simplex_tree +{ public: - using Base = Simplex_tree; + using Base = Simplex_tree; using Filtration_value = typename Base::Filtration_value; using Vertex_handle = typename Base::Vertex_handle; using Simplex_handle = typename Base::Simplex_handle; @@ -58,12 +60,26 @@ class Simplex_tree_interface : public Simplex_treeroot()->members_; @@ -102,25 +120,29 @@ class Simplex_tree_interface : public Simplex_tree& simplex, Filtration_value filtration = 0) { + bool insert_simplex(const std::vector& simplex, Filtration_value filtration = 0) + { Insertion_result result = Base::insert_simplex(simplex, filtration); return (result.second); } Filtration_value simplex_filtration(const Simplex& simplex) { return Base::filtration(Base::find(simplex)); } - void remove_maximal_simplex(const Simplex& simplex) { + void remove_maximal_simplex(const Simplex& simplex) + { Base::remove_maximal_simplex(Base::find(simplex)); Base::clear_filtration(); } - Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) { + Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) + { Simplex simplex; for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex.insert(simplex.begin(), vertex); @@ -128,7 +150,8 @@ class Simplex_tree_interface : public Simplex_treeefd = this->extend_filtration(); return; } + // CHANGED FROM THE ORIGINAL: Does collapses in a new simplex tree instead of inplace. Simplex_tree_interface* collapse_edges(int nb_collapse_iteration) { using Filtered_edge = std::tuple; std::vector edges; @@ -186,7 +212,8 @@ class Simplex_tree_interface : public Simplex_tree::const_iterator get_filtration_iterator_begin() { + typename std::vector::const_iterator get_filtration_iterator_begin() + { // Base::initialize_filtration(); already performed in filtration_simplex_range // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::filtration_simplex_range().begin(); } - typename std::vector::const_iterator get_filtration_iterator_end() { + typename std::vector::const_iterator get_filtration_iterator_end() + { // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::filtration_simplex_range().end(); } - Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) { + Skeleton_simplex_iterator get_skeleton_iterator_begin(int dimension) + { // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::skeleton_simplex_range(dimension).begin(); } - Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) { + Skeleton_simplex_iterator get_skeleton_iterator_end(int dimension) + { // this specific case works because the range is just a pair of iterators - won't work if range was a vector return Base::skeleton_simplex_range(dimension).end(); } - std::pair get_boundary_iterators(const Simplex& simplex) { + std::pair get_boundary_iterators(const Simplex& simplex) + { auto bd_sh = Base::find(simplex); if (bd_sh == Base::null_simplex()) throw std::runtime_error("simplex not found - cannot find boundaries"); // this specific case works because the range is just a pair of iterators - won't work if range was a vector diff --git a/multipers/gudhi/Simplex_tree_multi_interface.h b/multipers/gudhi/Simplex_tree_multi_interface.h index 69e50f7a..55521f8a 100644 --- a/multipers/gudhi/Simplex_tree_multi_interface.h +++ b/multipers/gudhi/Simplex_tree_multi_interface.h @@ -1,56 +1,89 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which - *is released under MIT. See file LICENSE or go to - *https://gudhi.inria.fr/licensing/ for full license details. Author(s): Vincent - *Rouvreau +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Vincent Rouvreau * * Copyright (C) 2016 Inria * * Modification(s): - * - 2022/11 David Loiseaux, Hannah Schreiber : adapt for - *multipersistence. + * - 2022/11 David Loiseaux, Hannah Schreiber : adapt for multipers. * - YYYY/MM Author: Description of the modification */ #pragma once -#include "Simplex_tree_interface.h" +#include #include #include -#include -// #include -#include "multiparameter_module_approximation/format_python-cpp.h" - #include #include #include +#include #include // std::pair #include #include // has_quiet_NaN +#include "Simplex_tree_interface.h" +#include "Persistence_slices_interface.h" +#include "gudhi/Multi_filtration/multi_filtration_utils.h" +#include "gudhi/multi_simplex_tree_helpers.h" +#include "../multiparameter_module_approximation/format_python-cpp.h" + namespace Gudhi { namespace multiparameter { namespace python_interface { +// Moved here as it was said unecessary for the gudhi version. TODO: either remove it from multipers if really not +// necessary, or find a better place for it. +/** + * \brief Applies a linear form (given by a scalar product, via Riesz + * representation) to the filtration values of the multiparameter simplextree to + * get a 1 parameter simplextree. \ingroup multiparameter \tparam + * simplextree_std A non-multi simplextree \tparam simplextree_multi A multi + * simplextree \param st Simplextree, with the same simplicial complex as + * st_multi, whose filtration has to be filled. \param st_multi Multiparameter + * simplextree to convert into a 1 parameter simplex tree. \param linear_form + * the linear form to apply. + * */ +template +void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const std::vector &linear_form) { + static_assert( + std::is_arithmetic_v && + Gudhi::multi_filtration::RangeTraits::is_multi_filtration, + "Can only convert multiparameter to non-multiparameter simplextree."); + auto sh = st.complex_simplex_range().begin(); + auto sh_multi = st_multi.complex_simplex_range().begin(); + auto end = st.complex_simplex_range().end(); + typename simplextree_multi::Options::Filtration_value multi_filtration; + for (; sh != end; ++sh, ++sh_multi) { + multi_filtration = st_multi.filtration(*sh_multi); + auto projected_filtration = compute_linear_projection(multi_filtration, linear_form); + st.assign_filtration(*sh, projected_filtration); + } +} + using interface_std = Simplex_tree; // Interface not necessary // (smaller so should do less // segfaults) -template -simplextreeinterface &get_simplextree_from_pointer(const uintptr_t splxptr) { // DANGER - simplextreeinterface &st = *(simplextreeinterface *)(splxptr); +template +Simplextree_interface &get_simplextree_from_pointer(const uintptr_t splxptr) { // DANGER + Simplextree_interface &st = *(Simplextree_interface *)(splxptr); return st; } -template +template ::is_multi_filtration> */> class Simplex_tree_multi_interface : public Simplex_tree_interface< Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration> { public: using SimplexTreeOptions = Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration; - using Python_filtration_type = std::vector; // TODO : - // std::conditional - using Base = Simplex_tree; - using Filtration_value = typename Base::Filtration_value; + // using Python_filtration_type = std::vector; // TODO : + // // std::conditional + using Base = Simplex_tree_interface; + using Base_tree = Simplex_tree; + using Filtration_value = Filtration; using Vertex_handle = typename Base::Vertex_handle; using Simplex_handle = typename Base::Simplex_handle; using Insertion_result = typename std::pair; @@ -61,73 +94,102 @@ class Simplex_tree_multi_interface using Complex_simplex_iterator = typename Base::Complex_simplex_iterator; using Extended_filtration_data = typename Base::Extended_filtration_data; using Boundary_simplex_iterator = typename Base::Boundary_simplex_iterator; - typedef bool (*blocker_func_t)(Simplex simplex, void *user_data); + using blocker_func_t = bool (*)(Simplex, void *); using euler_chars_type = std::vector; - public: static_assert(std::is_same_v, "value_type has to be the same as Filtration::value_type. This is only a hack for python"); Extended_filtration_data efd; + Simplex_tree_multi_interface() = default; + Simplex_tree_multi_interface(const Base& st) : Base(st) {}; + Simplex_tree_multi_interface(const Base_tree& st) : Base(st) {}; + Simplex_tree_multi_interface(Base&& st) : Base(std::move(st)) {}; + Simplex_tree_multi_interface(Base_tree&& st) : Base(std::move(st)) {}; + Simplex_tree_multi_interface& operator=(const Base& st){ + Base::operator=(st); + return *this; + } + Simplex_tree_multi_interface& operator=(const Base_tree& st){ + Base::operator=(st); + return *this; + } + Simplex_tree_multi_interface& operator=(Base&& st){ + Base::operator=(std::move(st)); + return *this; + } + Simplex_tree_multi_interface& operator=(Base_tree&& st){ + Base::operator=(std::move(st)); + return *this; + } + bool find_simplex(const Simplex &simplex) { return (Base::find(simplex) != Base::null_simplex()); } + int simplex_dimension(const Simplex &simplex) { + auto sh = Base_tree::find(simplex); + if (sh == Base_tree::null_simplex()) return -1; + return Base_tree::dimension(sh); + } + void assign_simplex_filtration(const Simplex &simplex, const Filtration_value &filtration) { Base::assign_filtration(Base::find(simplex), filtration); Base::clear_filtration(); } bool insert(const Simplex &simplex, const Filtration_value &filtration) { - Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + Insertion_result result = Base_tree::insert_simplex_and_subfaces(simplex, filtration, Base::Insertion_strategy::HIGHEST); if (result.first != Base::null_simplex()) Base::clear_filtration(); return (result.second); } + bool insert_force(const Simplex &simplex, const Filtration_value &filtration) { + Insertion_result result = Base_tree::insert_simplex_and_subfaces(simplex, filtration, Base::Insertion_strategy::FORCE); + Base::clear_filtration(); + return (result.second); + } + // Do not interface this function, only used in alpha complex interface for // complex creation bool insert_simplex(const Simplex &simplex, const Filtration_value &filtration) { - Insertion_result result = Base::insert_simplex(simplex, filtration); + Insertion_result result = Base_tree::insert_simplex(simplex, filtration); return (result.second); } - // bool insert_simplex(const Simplex& simplex, const Python_filtration_type& - // filtration ) { - // Filtration_value& filtration_ = *(Filtration_value*)(&filtration); // - // Jardinage for no copy. Insertion_result result = - // Base::insert_simplex(simplex, filtration); return (result.second); - // } + // bool insert_simplex(const Simplex &simplex, const Python_filtration_type &filtration) { + // Filtration_value &filtration_ = *(Filtration_value *)(&filtration); // Jardinage for no copy. + // Insertion_result result = Base::insert_simplex(simplex, filtration); + // return (result.second); + // } // Do not interface this function, only used in interface for complex creation bool insert_simplex_and_subfaces(const Simplex &simplex, const Filtration_value &filtration) { - Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + Insertion_result result = Base_tree::insert_simplex_and_subfaces(simplex, filtration, Base::Insertion_strategy::HIGHEST); return (result.second); } - // bool insert_simplex_and_subfaces(const Simplex& simplex, const - // Python_filtration_type& filtration ) { - // Filtration_value& filtration_ = *(Filtration_value*)(&filtration); // - // Jardinage for no copy. Insertion_result result = - // Base::insert_simplex_and_subfaces(simplex, filtration); return - // (result.second); - // } + // bool insert_simplex_and_subfaces(const Simplex &simplex, const Python_filtration_type &filtration) { + // Filtration_value &filtration_ = *(Filtration_value *)(&filtration); // Jardinage for no copy. + // Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration, Base::Insertion_strategy::HIGHEST); + // return (result.second); + // } // Do not interface this function, only used in strong witness interface for // complex creation - bool insert_simplex(const std::vector &simplex, const typename Filtration_value::Generator &filtration) { - Insertion_result result = Base::insert_simplex(simplex, filtration); + bool insert_simplex(const std::vector &simplex, const Filtration_value &filtration) { + Insertion_result result = Base_tree::insert_simplex(simplex, filtration); return (result.second); } // Do not interface this function, only used in strong witness interface for // complex creation bool insert_simplex_and_subfaces(const std::vector &simplex, const Filtration_value &filtration) { - Insertion_result result = Base::insert_simplex_and_subfaces(simplex, filtration); + Insertion_result result = Base_tree::insert_simplex_and_subfaces(simplex, filtration, Base::Insertion_strategy::HIGHEST); return (result.second); } typename SimplexTreeOptions::Filtration_value *simplex_filtration(const Simplex &simplex) { - auto &filtration = Base::filtration_mutable(Base::find(simplex)); - return &filtration; // We return the pointer to get a numpy view - // afterward + auto &filtration = Base::get_filtration_value(Base::find(simplex)); + return &filtration; // We return the pointer to get a numpy view afterward } Simplex_and_filtration get_simplex_and_filtration(Simplex_handle f_simplex) { @@ -138,7 +200,7 @@ class Simplex_tree_multi_interface auto it = Base::simplex_vertex_range(f_simplex); Simplex simplex(it.begin(), it.end()); std::reverse(simplex.begin(), simplex.end()); - return std::make_pair(std::move(simplex), &Base::filtration_mutable(f_simplex)); + return std::make_pair(std::move(simplex), &Base::get_filtration_value(f_simplex)); } Filtered_simplices get_star(const Simplex &simplex) { @@ -148,7 +210,7 @@ class Simplex_tree_multi_interface for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex_star.insert(simplex_star.begin(), vertex); } - star.push_back(std::make_pair(simplex_star, &Base::filtration_mutable(f_simplex))); + star.push_back(std::make_pair(simplex_star, &Base::get_filtration_value(f_simplex))); } return star; } @@ -160,7 +222,7 @@ class Simplex_tree_multi_interface for (auto vertex : Base::simplex_vertex_range(f_simplex)) { simplex_coface.insert(simplex_coface.begin(), vertex); } - cofaces.push_back(std::make_pair(simplex_coface, &Base::filtration_mutable(f_simplex))); + cofaces.push_back(std::make_pair(simplex_coface, &Base::get_filtration_value(f_simplex))); } return cofaces; } @@ -181,20 +243,19 @@ class Simplex_tree_multi_interface void set_key(const Simplex &simplex, int key) { Base::assign_key(Base::find(simplex), key); - return; } // Fills a parameter with a lower-star filtration - void fill_lowerstar(const std::vector &filtration, int axis) { + void fill_lowerstar(const std::vector &filtration, int axis) { /* constexpr value_type minus_inf = * -1*std::numeric_limits::infinity(); */ std::vector filtration_values_of_vertex; for (auto &SimplexHandle : Base::complex_simplex_range()) { - auto ¤t_birth = Base::filtration_mutable(SimplexHandle); + auto ¤t_birth = Base::get_filtration_value(SimplexHandle); /* value_type to_assign = minus_inf; */ filtration_values_of_vertex.clear(); for (auto vertex : Base::simplex_vertex_range(SimplexHandle)) { - if constexpr (std::numeric_limits::has_quiet_NaN) + if constexpr (std::numeric_limits::has_quiet_NaN) /* to_assign = std::max(filtration[vertex], to_assign); */ if (std::isnan(filtration[vertex])) std::cerr << "Invalid filtration for vertex " << vertex << " !!" << std::endl; @@ -203,11 +264,35 @@ class Simplex_tree_multi_interface value_type to_assign = *std::max_element(filtration_values_of_vertex.begin(), filtration_values_of_vertex.end()); /* if (to_assign >10 || to_assign < -10 ) */ /* std::cout <<"to_assign : "<< to_assign << std::endl; */ - current_birth[axis] = to_assign; + current_birth(0, axis) = to_assign; // Base::assign_filtration(SimplexHandle, current_birth); } } + // // Fills a parameter with a lower-star filtration + // void fill_lowerstar(const Filtration_value &filtration, int axis) { + // /* constexpr value_type minus_inf = + // * -1*std::numeric_limits::infinity(); */ + // std::vector filtration_values_of_vertex; + // for (auto &SimplexHandle : Base::complex_simplex_range()) { + // auto ¤t_birth = Base::get_filtration_value(SimplexHandle); + // /* value_type to_assign = minus_inf; */ + // filtration_values_of_vertex.clear(); + // for (auto vertex : Base::simplex_vertex_range(SimplexHandle)) { + // if constexpr (std::numeric_limits::has_quiet_NaN) + // /* to_assign = std::max(filtration[vertex], to_assign); */ + // if (std::isnan(filtration(0,vertex))) + // std::cerr << "Invalid filtration for vertex " << vertex << " !!" << std::endl; + // filtration_values_of_vertex.push_back(filtration(0,vertex)); + // } + // value_type to_assign = *std::max_element(filtration_values_of_vertex.begin(), filtration_values_of_vertex.end()); + // /* if (to_assign >10 || to_assign < -10 ) */ + // /* std::cout <<"to_assign : "<< to_assign << std::endl; */ + // current_birth(0, axis) = to_assign; + // // Base::assign_filtration(SimplexHandle, current_birth); + // } + // } + using simplices_list = std::vector>; simplices_list get_simplices_of_dimension(int dimension) { @@ -236,7 +321,7 @@ class Simplex_tree_multi_interface auto it = Base::simplex_vertex_range(simplexHandle).begin(); simplex = {*it, *(++it)}; const auto &f = Base::filtration(simplexHandle); - simplex_list.push_back({simplex, {static_cast(f[0]), static_cast(f[1])}}); + simplex_list.push_back({simplex, {static_cast(f(0, 0)), static_cast(f(0, 1))}}); } } return simplex_list; @@ -245,22 +330,34 @@ class Simplex_tree_multi_interface void resize_all_filtrations(int num) { if (num < 0) return; for (const auto &SimplexHandle : Base::complex_simplex_range()) { - if constexpr (Filtration::is_multi_critical) { - for (auto &stuff : Base::filtration_mutable(SimplexHandle)) stuff.resize(num); - } else - Base::filtration_mutable(SimplexHandle).resize(num); + auto& f = Base::get_filtration_value(SimplexHandle); + if (f.num_parameters() == static_cast(num)) { + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) { + for (unsigned int g = 0; g < f.num_generators(); ++g) f.force_generator_size_to_number_of_parameters(g); + } + } else { + std::vector values(num * f.num_generators()); + unsigned int i = 0; + for (unsigned int g = 0; g < f.num_generators(); ++g){ + for (unsigned int p = 0; p < static_cast(num); ++p){ + if (p < f.num_parameters()) values[i] = f(g, p); + ++i; + } + } + f = Filtration_value(values.begin(), values.end(), num); + } } } - void from_std(char *buffer_start, std::size_t buffer_size, int dimension, const Filtration &default_values) { + void from_std(char *buffer_start, std::size_t buffer_size, int dimension, const Filtration_value &default_values) { interface_std st; st.deserialize(buffer_start, buffer_size); - Gudhi::multi_persistence::multify(st, *this, dimension, default_values); + *this = Gudhi::multi_persistence::make_multi_dimensional(st, default_values, dimension); } template void to_std(intptr_t ptr, const Line_like &line, int dimension) { - interface_std &st = get_simplextree_from_pointer(ptr); + auto &st = get_simplextree_from_pointer(ptr); for (const auto &simplex_handle : this->complex_simplex_range()) { std::vector simplex; @@ -275,17 +372,17 @@ class Simplex_tree_multi_interface } void to_std_linear_projection(intptr_t ptr, std::vector linear_form) { - interface_std &st = get_simplextree_from_pointer(ptr); - Gudhi::multi_persistence::linear_projection(st, *this, linear_form); + auto &st = get_simplextree_from_pointer(ptr); + linear_projection(st, *this, linear_form); } void squeeze_filtration_inplace(const std::vector> &grid, const bool coordinate_values = true) { - std::size_t num_parameters = this->get_number_of_parameters(); + std::size_t num_parameters = Base::num_parameters(); if (grid.size() != num_parameters) { throw std::invalid_argument("Grid and simplextree do not agree on number of parameters."); } - for (const auto &simplex_handle : this->complex_simplex_range()) { - auto &simplex_filtration = this->filtration_mutable(simplex_handle); + for (const auto &simplex_handle : Base::complex_simplex_range()) { + auto &simplex_filtration = Base::get_filtration_value(simplex_handle); const auto &coords = compute_coordinates_in_grid(simplex_filtration, grid); if (coordinate_values) simplex_filtration = coords.template as_type(); @@ -296,50 +393,49 @@ class Simplex_tree_multi_interface void unsqueeze_filtration(const intptr_t grid_st_ptr, const std::vector> &grid) { // TODO : this is const but GUDHI - if constexpr (Filtration::is_multicritical()) - throw std::invalid_argument("Multicritical not supported yet"); - else { - constexpr const bool verbose = false; - using int_fil_type = decltype(std::declval().template as_type()); - using st_coord_type = Simplex_tree_multi_interface; - st_coord_type &grid_st = *(st_coord_type *)grid_st_ptr; // TODO : maybe fix this. - std::vector simplex_vertex; - int num_parameters = grid_st.get_number_of_parameters(); - for (auto &simplex_handle : grid_st.complex_simplex_range()) { - const auto &simplex_filtration = grid_st.filtration(simplex_handle); - if constexpr (verbose) std::cout << "Filtration " << simplex_filtration << "\n"; - Filtration splx_filtration(simplex_filtration.size(), 1.); - if (simplex_filtration.is_finite()) { - for (auto i : std::views::iota(num_parameters)) splx_filtration[i] = grid[i][simplex_filtration[i]]; - } else if (simplex_filtration.is_plus_inf()) { - splx_filtration = Filtration().inf(); - } else if (simplex_filtration.is_minus_inf()) { - splx_filtration = Filtration().minus_inf(); - } else if (simplex_filtration.is_nan()) { - splx_filtration = Filtration().nan(); - } - if constexpr (verbose) std::cout << "Filtration " << splx_filtration << "\n"; - for (const auto s : grid_st.simplex_vertex_range(simplex_handle)) simplex_vertex.push_back(s); - this->insert_simplex(simplex_vertex, splx_filtration); - if constexpr (verbose) std::cout << "Coords in st" << this->filtration(this->find(simplex_vertex)) << std::endl; - simplex_vertex.clear(); + constexpr const bool verbose = false; + using int_fil_type = decltype(std::declval().template as_type()); + using st_coord_type = Simplex_tree_multi_interface; + st_coord_type &grid_st = *(st_coord_type *)grid_st_ptr; // TODO : maybe fix this. + std::vector simplex_vertex; + int num_parameters = grid_st.num_parameters(); + for (auto &simplex_handle : grid_st.complex_simplex_range()) { + const auto &simplex_filtration = grid_st.filtration(simplex_handle); + // if you can be sure that the used filtration values will always be 1-critical whatever the use, + // a static_assert/constexpr with condition Filtration_value::ensures_1_criticality() can be used instead. + if (simplex_filtration.num_generators() > 1) throw std::invalid_argument("Multicritical not supported yet"); + if constexpr (verbose) std::cout << "Filtration_value " << simplex_filtration << "\n"; + Filtration_value splx_filtration(simplex_filtration.num_parameters(), 1.); + if (simplex_filtration.is_finite()) { + for (auto i : std::views::iota(num_parameters)) splx_filtration(0,i) = grid[i][simplex_filtration(0,i)]; + } else if (simplex_filtration.is_plus_inf()) { + splx_filtration = Filtration_value::inf(num_parameters); + } else if (simplex_filtration.is_minus_inf()) { + splx_filtration = Filtration_value::minus_inf(num_parameters); + } else if (simplex_filtration.is_nan()) { + splx_filtration = Filtration_value::nan(num_parameters); } + if constexpr (verbose) std::cout << "Filtration_value " << splx_filtration << "\n"; + for (const auto s : grid_st.simplex_vertex_range(simplex_handle)) simplex_vertex.push_back(s); + insert_simplex(simplex_vertex, splx_filtration); + if constexpr (verbose) std::cout << "Coords in st" << Base::filtration(Base::find(simplex_vertex)) << std::endl; + simplex_vertex.clear(); } } void squeeze_filtration(const intptr_t outptr, const std::vector> &grid) { // TODO : this is const but GUDHI constexpr const bool verbose = false; - using int_fil_type = decltype(std::declval().template as_type()); + using int_fil_type = decltype(std::declval().template as_type()); using st_coord_type = Simplex_tree_multi_interface; st_coord_type &out = *(st_coord_type *)outptr; // TODO : maybe fix this. std::vector simplex_vertex; - for (const auto &simplex_handle : this->complex_simplex_range()) { - const auto &simplex_filtration = this->filtration(simplex_handle); - if constexpr (verbose) std::cout << "Filtration " << simplex_filtration << "\n"; + for (const auto &simplex_handle : Base::complex_simplex_range()) { + const auto &simplex_filtration = Base::filtration(simplex_handle); + if constexpr (verbose) std::cout << "Filtration_value " << simplex_filtration << "\n"; const auto &coords = compute_coordinates_in_grid(simplex_filtration, grid); if constexpr (verbose) std::cout << "Coords " << coords << "\n"; - for (auto s : this->simplex_vertex_range(simplex_handle)) simplex_vertex.push_back(s); + for (auto s : Base::simplex_vertex_range(simplex_handle)) simplex_vertex.push_back(s); out.insert_simplex(simplex_vertex, coords); if constexpr (verbose) std::cout << "Coords in st" << out.filtration(out.find(simplex_vertex)) << std::endl; simplex_vertex.clear(); @@ -349,29 +445,25 @@ class Simplex_tree_multi_interface std::vector>> // dim, pts, param get_filtration_values(const std::vector °rees) { using multi_filtration_grid = std::vector>; - int num_parameters = this->get_number_of_parameters(); + int num_parameters = Base::num_parameters(); std::vector out(degrees.size(), multi_filtration_grid(num_parameters)); - std::vector degree_index(this->dimension() + 1); + std::vector degree_index(Base::dimension() + 1); int count = 0; for (auto degree : degrees) { degree_index[degree] = count++; - out[degree_index[degree]].reserve(this->num_simplices()); + out[degree_index[degree]].reserve(Base::num_simplices()); } - for (const auto &simplex_handle : this->complex_simplex_range()) { - const auto &filtration = this->filtration(simplex_handle); - const auto degree = this->dimension(simplex_handle); + for (const auto &simplex_handle : Base::complex_simplex_range()) { + const auto &filtration = Base::filtration(simplex_handle); + const auto degree = Base::dimension(simplex_handle); if (std::find(degrees.begin(), degrees.end(), degree) == degrees.end()) continue; // for (int parameter = 0; parameter < num_parameters; parameter++) { // out[degree_index[degree]][parameter].push_back(filtration[parameter]); // } - if constexpr (Filtration::is_multi_critical) - for (std::size_t i = 0; i < filtration.num_generators(); i++) + for (std::size_t i = 0; i < filtration.num_generators(); i++) for (int parameter = 0; parameter < num_parameters; parameter++) - out[degree_index[degree]][parameter].push_back(filtration[i][parameter]); - else - for (int parameter = 0; parameter < num_parameters; parameter++) - out[degree_index[degree]][parameter].push_back(filtration[parameter]); + out[degree_index[degree]][parameter].push_back(filtration(i, parameter)); } return out; } @@ -381,52 +473,52 @@ class Simplex_tree_multi_interface using scc_type = mma::scc_type; - scc_type inline simplextree_to_scc() { return Gudhi::multiparameter::mma::simplextree_to_scc(*this); } + scc_type simplextree_to_scc() { return Gudhi::multiparameter::mma::simplextree_to_scc(*this); } using kscc_type = mma::kscc_type; - kscc_type inline kcritical_simplextree_to_scc() { - if constexpr (SimplexTreeOptions::Filtration_value::is_multi_critical) - return Gudhi::multiparameter::mma::kcritical_simplextree_to_scc(*this); - else - return {}; + kscc_type kcritical_simplextree_to_scc() { + return Gudhi::multiparameter::mma::kcritical_simplextree_to_scc(*this); } using function_scc_type = std::vector>>, boundary_matrix>>; - function_scc_type inline function_simplextree_to_scc() { + function_scc_type function_simplextree_to_scc() { return Gudhi::multiparameter::mma::function_simplextree_to_scc(*this); } using flattened_scc_type = std::pair>, std::vector>>; - flattened_scc_type inline simplextree_to_ordered_bf() { - return Gudhi::multiparameter::mma::simplextree_to_ordered_bf(*this); + flattened_scc_type simplextree_to_ordered_bf() { + return Gudhi::multiparameter::mma::simplextree_to_ordered_bf(*this); } // Diff / grid stuff - using idx_map_type = std::vector>; + using idx_map_type = std::vector>; - inline idx_map_type build_idx_map(const std::vector &simplices_dimensions) { - static_assert(!Filtration::is_multi_critical, "Multicritical not supported yet"); - auto num_parameters = this->get_number_of_parameters(); + idx_map_type build_idx_map(const std::vector &simplices_dimensions) { + // static_assert(!Filtration_value::is_multi_critical, "Multicritical not supported yet"); + auto num_parameters = Base::num_parameters(); if (static_cast(simplices_dimensions.size()) < num_parameters) throw; - int max_dim = *std::max_element(simplices_dimensions.begin(), simplices_dimensions.end()); - int min_dim = *std::min_element(simplices_dimensions.begin(), simplices_dimensions.end()); - max_dim = min_dim >= 0 ? max_dim : this->dimension(); + int max_dim = *std::ranges::max_element(simplices_dimensions.begin(), simplices_dimensions.end()); + int min_dim = *std::ranges::min_element(simplices_dimensions.begin(), simplices_dimensions.end()); + max_dim = min_dim >= 0 ? max_dim : Base::dimension(); idx_map_type idx_map(num_parameters); auto splx_idx = 0u; - for (auto sh : this->complex_simplex_range()) { // order has to be retrieved later, so I'm + for (auto sh : Base::complex_simplex_range()) { // order has to be retrieved later, so I'm // not sure that skeleton iterator is well // suited - const auto &splx_filtration = this->filtration(sh); - const auto splx_dim = this->dimension(sh); + const auto &splx_filtration = Base::filtration(sh); + // if you can be sure that the used filtration values will always be 1-critical whatever the use, + // a static_assert with condition Filtration_value::ensures_1_criticality() can be used instead. + if (splx_filtration.num_generators() > 1) throw std::invalid_argument("Multicritical not supported yet"); + const auto splx_dim = Base::dimension(sh); if (splx_dim <= max_dim) - for (auto i = 0u; i < splx_filtration.size(); i++) { + for (auto i = 0u; i < splx_filtration.num_parameters(); i++) { if (simplices_dimensions[i] != splx_dim && simplices_dimensions[i] != -1) continue; - auto f = splx_filtration[i]; + auto f = splx_filtration(0, i); idx_map[i].try_emplace(f, splx_idx); } splx_idx++; @@ -436,10 +528,10 @@ class Simplex_tree_multi_interface using pts_indices_type = std::vector>; - static std::pair inline get_pts_indices( + static std::pair get_pts_indices( const idx_map_type &idx_map, - const std::vector> &pts) { - static_assert(!Filtration::is_multi_critical, "Multicritical not supported yet"); + const std::vector> &pts) { + // static_assert(!Filtration_value::is_multi_critical, "Multicritical not supported yet"); std::size_t num_pts = pts.size(); std::size_t num_parameters = idx_map.size(); pts_indices_type out_indices(num_pts, @@ -464,8 +556,8 @@ class Simplex_tree_multi_interface return {out_indices, out_unmapped_values}; // TODO return a ptr for python } - std::pair inline pts_to_indices( - const std::vector> &pts, + std::pair pts_to_indices( + const std::vector> &pts, const std::vector &simplices_dimensions) { return get_pts_indices(this->build_idx_map(simplices_dimensions), pts); } @@ -479,48 +571,47 @@ using interface_multi = Simplex_tree_multi_interface< // only" python interface template void inline flatten_diag_from_ptr(const uintptr_t splxptr, - const uintptr_t newsplxptr, + const uintptr_t new_splxptr, const std::vector basepoint, int dimension) { // for python - auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st = get_simplextree_from_pointer(new_splxptr); auto &st_multi = get_simplextree_from_pointer>(splxptr); flatten_diag(st, st_multi, basepoint, dimension); } template void inline multify_from_ptr(uintptr_t splxptr, - uintptr_t newsplxptr, + uintptr_t new_splxptr, const int dimension, const Filtration &default_values) { // for python auto &st = get_simplextree_from_pointer(splxptr); - auto &st_multi = get_simplextree_from_pointer>(newsplxptr); - Gudhi::multi_persistence::multify(st, st_multi, dimension, default_values); + auto &st_multi = get_simplextree_from_pointer>(new_splxptr); + st_multi = Gudhi::multi_persistence::make_multi_dimensional::SimplexTreeOptions>(st, default_values, dimension); } template void inline flatten_from_ptr(uintptr_t splxptr, - uintptr_t newsplxptr, + uintptr_t new_splxptr, const int dimension = 0) { // for python - auto &st = get_simplextree_from_pointer(newsplxptr); + auto &st = get_simplextree_from_pointer(new_splxptr); auto &st_multi = get_simplextree_from_pointer>(splxptr); - Gudhi::multi_persistence::flatten(st, st_multi, dimension); + st = Gudhi::multi_persistence::make_one_dimensional(st_multi, dimension); } template void inline linear_projection_from_ptr(const uintptr_t ptr, const uintptr_t ptr_multi, Args... args) { auto &st = get_simplextree_from_pointer(ptr); auto &st_multi = get_simplextree_from_pointer>(ptr_multi); - Gudhi::multi_persistence::linear_projection(st, st_multi, args...); + linear_projection(st, st_multi, args...); } -template > +template > using options_multi = Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration; template void inline squeeze_filtration_from_ptr(uintptr_t splxptr, Args... args) { Simplex_tree> &st_multi = *(Gudhi::Simplex_tree> *)(splxptr); squeeze_filtration(st_multi, args...); - return; } template diff --git a/multipers/gudhi/cubical_to_boundary.h b/multipers/gudhi/cubical_to_boundary.h deleted file mode 100644 index 1b0bf9b7..00000000 --- a/multipers/gudhi/cubical_to_boundary.h +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef MULTIPERS_CUBICAL_CONV_H -#define MULTIPERS_CUBICAL_CONV_H - -#include -#include - -#include - -inline void _to_boundary(const std::vector& shape, - std::vector >& generator_maps, - std::vector& generator_dimensions) { - using Bitmap_cubical_complex_base = Gudhi::cubical_complex::Bitmap_cubical_complex_base; - using Bitmap_cubical_complex = Gudhi::cubical_complex::Bitmap_cubical_complex; - using Simplex_handle = Bitmap_cubical_complex::Simplex_handle; - - if (shape.empty()) return; - - unsigned int size = 1; - for (auto v : shape) size *= v; - - std::vector vertices(size); - Bitmap_cubical_complex cub(shape, vertices, false); - - unsigned int dimMax = shape.size(); - std::vector > faces(dimMax + 1); - unsigned int numberOfSimplices = 0; - for (unsigned int d = 0; d < dimMax + 1; ++d) { - for ([[maybe_unused]] auto sh : cub.skeleton_simplex_range(d)) { - ++numberOfSimplices; - } - } - - generator_dimensions.resize(numberOfSimplices); - generator_maps.resize(numberOfSimplices); - unsigned int i = 0; - for (unsigned int d = 0; d < dimMax + 1; ++d) { - for (auto sh : cub.skeleton_simplex_range(d)) { - cub.assign_key(sh, i); - generator_dimensions[i] = d; - auto& col = generator_maps[i]; - for (auto b : cub.boundary_simplex_range(sh)) col.push_back(cub.key(b)); - std::sort(col.begin(), col.end()); - ++i; - } - } -}; - -inline void get_vertices(unsigned int i, - std::set& vertices, - const std::vector >& generator_maps) { - if (generator_maps[i].empty()) { - vertices.insert(i); - return; - } - - for (auto v : generator_maps[i]) get_vertices(v, vertices, generator_maps); -} - -#endif // MULTIPERS_CUBICAL_CONV_H diff --git a/multipers/gudhi/gudhi/Degree_rips_bifiltration.h b/multipers/gudhi/gudhi/Degree_rips_bifiltration.h new file mode 100644 index 00000000..711fc376 --- /dev/null +++ b/multipers/gudhi/gudhi/Degree_rips_bifiltration.h @@ -0,0 +1,2286 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, David Loiseaux + * + * Copyright (C) 2024-25 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Degree_rips_bifiltration.h + * @author Hannah Schreiber, David Loiseaux + * @brief Contains the @ref Gudhi::multi_filtration::Degree_rips_bifiltration class. + */ + +#ifndef MF_DEGREE_RIPS_BIFILTRATION_H_ +#define MF_DEGREE_RIPS_BIFILTRATION_H_ + +#include //std::lower_bound +#include //std::isnan, std::min +#include //std::size_t +#include //std::int32_t +#include //memcpy +#include //std::distance +#include //std::ostream +#include //std::numerical_limits +#include //std::logic_error +#include //std::is_arithmetic +#include //std::swap, std::move +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace Gudhi::multi_filtration { + +/** + * @class Degree_rips_bifiltration Degree_rips_bifiltration.h gudhi/Degree_rips_bifiltration.h + * @ingroup multi_filtration + * + * @brief Class encoding the different generators, i.e., apparition times, of a \f$ k \f$-critical + * \f$\mathbb R^2\f$-filtration value in a degree-Rips filtration. That is, a \f$ k \f$-critical filtration value is + * always of the form: \f$ [(v_0,0), (v_1,1), (v_2,2), ..., (v_{k-1},k-1)] \f$, where all pairs \f$ (v_i,i) \f$ + * represent a generator with two parameters: the radius and the degree. More precisely: let \f$ d \f$ be the max + * degree of a vertex in the complex. A vertex will be \f$ k \f$-critical if it has degree \f$ d - k + 1 \f$ and an + * edge is \f$ k \f$-critical if one of its end vertices is \f$ k \f$-critical and the other one \f$ j \f$-critical, + * \f$ j \geq k \f$. The first parameter is the more standard radius parameter of a Rips filtration. + * Note that the set of generators does not have to be minimal (contrary to @ref Multi_parameter_filtration e.g.), + * neither ordered lexicographically. + * Implements the concept @ref FiltrationValue of the @ref Gudhi::Simplex_tree and the concept + * @ref Gudhi::multi_persistence::MultiFiltrationValue. + * + * @details Overloads `std::numeric_limits` such that: + * - `std::numeric_limits::has_infinity` returns `true` if and only if `Co` is false, + * - `std::numeric_limits::has_quiet_NaN` returns `true`, + * - `std::numeric_limits::infinity()` returns + * @ref Degree_rips_bifiltration::inf() "", + * - `std::numeric_limits::minus_infinity()` returns + * @ref Degree_rips_bifiltration::minus_inf() "", + * - `std::numeric_limits::max(num_param)` throws if `Co` is true and otherwise returns a + * @ref Degree_rips_bifiltration with 1 generators with first parameter 0 and second parameter + *`std::numeric_limits::max()`, + * - `std::numeric_limits::quiet_NaN()` returns @ref Degree_rips_bifiltration::nan(). + * + * @tparam T Arithmetic type of an entry of the second parameter of a filtration value. Has to be **signed** and + * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, + * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. + * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` + * can simply throw. Examples are the native types `double`, `float` and `int`. + * @tparam Co If `true`, reverses the poset order, i.e., the order \f$ \le \f$ in \f$ \mathbb R^n \f$ becomes + * \f$ \ge \f$. That is, the positive cones representing a lifetime become all negative instead. + * @tparam Ensure1Criticality If `true`, the methods ensure that the filtration value is always 1-critical by throwing + * or refusing to compile if a modification increases the number of generators. + */ +template +class Degree_rips_bifiltration +{ + public: + using Underlying_container = std::vector; /**< Underlying container for values. */ + + // CONSTRUCTORS + + /** + * @brief Default constructor. Builds filtration value with one generator `(val, 0)`. + * If Co is false, `val` is -inf, if Co is true, `val` is at +inf. + * + * @param number_of_parameters Ignored, the number of parameters is always 2. For interface purposes only. + */ + Degree_rips_bifiltration([[maybe_unused]] int number_of_parameters = 2) : generators_(1, _get_default_value()) {} + + explicit Degree_rips_bifiltration(Gudhi::simplex_tree::empty_filtration_value_t /*e*/) : generators_(0) {} + + /** + * @brief Builds a filtration value with one generator `(value, 0)`. + * + * @param number_of_parameters Ignored, the number of parameters is always 2. For interface purposes only. + * @param value Initialization value for the second parameter. + */ + Degree_rips_bifiltration([[maybe_unused]] int number_of_parameters, T value) : generators_(1, value) {} + + /** + * @brief Builds filtration value with one generator `(val, i)`, where `val` and `i` are the two first elements + * of the given range. Note that `i` has to be 0. + * + * @tparam ValueRange Range of types convertible to `T`. Should have a begin() method. + * @param range Values of the generator. The range has to have at least two elements. + */ + template , class = std::enable_if_t::has_begin> > + Degree_rips_bifiltration(const ValueRange &range) : generators_(1, *(range.begin())) + { + GUDHI_CHECK(*(range.begin() + 1) == 0, std::invalid_argument("Second value of the range has to be 0")); + } + + /** + * @brief Builds filtration value with one generator `(val, i)`, where `val` and `i` are the two first elements + * of the given range. Note that `i` has to be 0. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + */ + template > > + Degree_rips_bifiltration(Iterator it_begin, [[maybe_unused]] Iterator it_end) : generators_(1, *it_begin) + { + GUDHI_CHECK(*(it_begin + 1) == 0, std::invalid_argument("Second value of the range has to be 0")); + } + + /** + * @brief Builds a filtration value with given values from the given range. The two first elements of the range have + * to correspond to the first generator, the two next elements to the second generator and so on... So the length of + * the range has to be a multiple of 2 and the number of generators will be \f$ k = length / 2 \f$. Note that starting + * from the second element, every second element has to represent the continuous sequence from 0 to \f$ k \f$. + * The range is represented by two iterators. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyForwardIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + * @param number_of_parameters Ignored, the number of parameters is always 2. For interface purposes only. + */ + template > > + Degree_rips_bifiltration(Iterator it_begin, Iterator it_end, [[maybe_unused]] int number_of_parameters) + : generators_() + { + size_type num_gen = std::distance(it_begin, it_end) / 2; + if constexpr (Ensure1Criticality) { + if (num_gen > 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + generators_.resize(num_gen); + Iterator it = it_begin; + for (size_type i = 0; i < num_gen; ++i) { + generators_[i] = *it; + ++it; + GUDHI_CHECK( + static_cast(*it) == i, + std::invalid_argument( + "Every second value of the range has to correspond to a contiguous sequence of integers starting at 0.")); + ++it; + } + } + + /** + * @brief Builds filtration value with given values from the given range. The range only represent the values of the + * first parameter. So `(generators[0], 0)` is the first generator, `(generators[1], 1)` is the second generator and + * so on... The range is represented by @ref Degree_rips_bifiltration::Underlying_container "" and copied into the + * underlying container of the class. + * + * @param generators Values for the second parameter. + * @param number_of_parameters Ignored, the number of parameters is always 2. For interface purposes only. + */ + Degree_rips_bifiltration(const Underlying_container &generators, [[maybe_unused]] int number_of_parameters) + : generators_(generators) + { + if constexpr (Ensure1Criticality) { + if (generators_.size() > 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Builds filtration value with given values from the given range. The range only represent the values of the + * first parameter. So `(generators[0], 0)` is the first generator, `(generators[1], 1)` is the second generator and + * so on... The range is represented by @ref Degree_rips_bifiltration::Underlying_container "" and **moved** into + * the underlying container of the class. + * + * @param generators Values to move. + * @param number_of_parameters Ignored, the number of parameters is always 2. For interface purposes only. + */ + Degree_rips_bifiltration(Underlying_container &&generators, [[maybe_unused]] int number_of_parameters) + : generators_(std::move(generators)) + { + if constexpr (Ensure1Criticality) { + if (generators_.size() > 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + // cannot use = default as it triggers "dummy_g_ may be used uninitialized" compiler warning for nothing + /** + * @brief Copy constructor. + */ + Degree_rips_bifiltration(const Degree_rips_bifiltration &other) : generators_(other.generators_) {} + + // cannot use = default as it triggers "dummy_g_ may be used uninitialized" compiler warning for nothing + /** + * @brief Move constructor. + */ + Degree_rips_bifiltration(Degree_rips_bifiltration &&other) noexcept : generators_(std::move(other.generators_)) {} + + /** + * @brief Copy constructor. + * + * @tparam U Type convertible into `T`. + */ + template + Degree_rips_bifiltration(const Degree_rips_bifiltration &other) + : generators_(other.begin(), other.end()) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (generators_.size() > 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + ~Degree_rips_bifiltration() = default; + + // cannot use = default as it triggers "dummy_g_ may be used uninitialized" compiler warning for nothing + /** + * @brief Assign operator. + */ + Degree_rips_bifiltration &operator=(const Degree_rips_bifiltration &other) { + generators_ = other.generators_; + return *this; + } + + // cannot use = default as it triggers "dummy_g_ may be used uninitialized" compiler warning for nothing + /** + * @brief Move assign operator. + */ + Degree_rips_bifiltration &operator=(Degree_rips_bifiltration &&other) noexcept { + generators_ = std::move(other.generators_); + return *this; + } + + /** + * @brief Assign operator. + * + * @tparam U Type convertible into `T`. + */ + template + Degree_rips_bifiltration &operator=(const Degree_rips_bifiltration &other) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (other.num_generators() > 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + generators_ = Underlying_container(other.begin(), other.end()); + return *this; + } + + /** + * @brief Swap operator. + */ + friend void swap(Degree_rips_bifiltration &f1, Degree_rips_bifiltration &f2) noexcept + { + f1.generators_.swap(f2.generators_); + } + + // VECTOR-LIKE + + using value_type = T; /**< Value type. */ + using size_type = typename Underlying_container::size_type; /**< Size type. */ + using difference_type = typename Underlying_container::difference_type; /**< Difference type. */ + using reference = value_type &; /**< Reference type. */ + using const_reference = const value_type &; /**< Const reference type. */ + using pointer = typename Underlying_container::pointer; /**< Pointer type. */ + using const_pointer = typename Underlying_container::const_pointer; /**< Const pointer type. */ + using iterator = typename Underlying_container::iterator; /**< Iterator type. */ + using const_iterator = typename Underlying_container::const_iterator; /**< Const iterator type. */ + using reverse_iterator = typename Underlying_container::reverse_iterator; /**< Reverse iterator type. */ + using const_reverse_iterator = typename Underlying_container::const_reverse_iterator; /**< Const reverse iterator. */ + + /** + * @brief Returns reference to value of parameter `p` of generator `g`. + * + * The reference returned when `p` is 1 can be modified but will have no impact on the value of the second parameter + * represented on the class and is shared by the second parameter of all generators. If there is a need to store this + * value, a copy should be stored instead. + */ + reference operator()(size_type g, size_type p) + { + GUDHI_CHECK(g < generators_.size() && p < 2, std::out_of_range("Out of bound index.")); + if (p == 1) { + dummy_g_ = g; + return dummy_g_; + } + return generators_[g]; + } + + /** + * @brief Returns const reference to value of parameter `p` of generator `g`. + */ + const_reference operator()(size_type g, size_type p) const + { + GUDHI_CHECK(g < generators_.size() && p < 2, std::out_of_range("Out of bound index.")); + if (p == 1) { + dummy_g_ = g; + return dummy_g_; + } + return generators_[g]; + } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * + * The reference returned when `p` is 1 can be modified but will have no impact on the value of the second parameter + * represented on the class and is shared by the second parameter of all generators. If there is a need to store this + * value, a copy should be stored instead. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + reference operator[](const IndexRange &indices) + { + GUDHI_CHECK(indices.size() == 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return this->operator()(g, *(++it)); + } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + const_reference operator[](const IndexRange &indices) const + { + GUDHI_CHECK(indices.size() == 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return this->operator()(g, *(++it)); + } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The element `val_i` at index `i` + * corresponds to the first parameter of the generator `(val_i, i)`. + */ + iterator begin() noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The element `val_i` at index `i` + * corresponds to the first parameter of the generator `(val_i, i)`. + */ + const_iterator begin() const noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The element `val_i` at index `i` + * corresponds to the first parameter of the generator `(val_i, i)`. + */ + const_iterator cbegin() const noexcept { return generators_.cbegin(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + iterator end() noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator end() const noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator cend() const noexcept { return generators_.cend(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The element `val_i` at index `i` corresponds to the first parameter of the generator `(val_i, i)`. + */ + reverse_iterator rbegin() noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The element `val_i` at index `i` corresponds to the first parameter of the generator `(val_i, i)`. + */ + const_reverse_iterator rbegin() const noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The element `val_i` at index `i` corresponds to the first parameter of the generator `(val_i, i)`. + */ + const_reverse_iterator crbegin() const noexcept { return generators_.crbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + reverse_iterator rend() noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator rend() const noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator crend() const noexcept { return generators_.crend(); } + + /** + * @brief Returns the size of the underlying container. Corresponds exactly to @ref num_generators(), but enables + * to use the class as a classic range with a `begin`, `end` and `size` method. + */ + size_type size() const noexcept { return generators_.size(); } + + /** + * @brief Reserves space for the given number of generators in the underlying container. Does nothing if + * `Ensure1Criticality` is true. + */ + void reserve([[maybe_unused]] size_type number_of_generators) + { + if constexpr (Ensure1Criticality) { + return; + } else { + generators_.reserve(number_of_generators); + } + } + + // CONVERTERS + + // like numpy + /** + * @brief Returns a copy with entries casted into the type given as template parameter. + * + * @tparam U New type for the entries. + * @tparam OCo New value for `Co`. Default value: `Co`. + * @tparam OEns New value for `Ensure1Criticality`. Note that if `OEns` is set to true and the value is not + * 1-critical, the method will throw. Default value: `Ensure1Criticality`. + * @return Copy with new entry type. + */ + template + Degree_rips_bifiltration as_type() const + { + std::vector out(generators_.begin(), generators_.end()); + return Degree_rips_bifiltration(std::move(out), num_parameters()); + } + + /** + * @brief Converts the filtration value to @ref Multi_parameter_filtration without any set simplification. + * @warning The filtration value is converted one to one and is not simplified to a minimal set of generators or + * ordered by lexicographical order, that undefines the behaviour of some methods of the class. + * Use @ref as_type(const Degree_rips_bifiltration&) instead if needed. + */ + Multi_parameter_filtration convert_to_non_simplified_multi_parameter_filtration() const + { + std::vector out(generators_.size() * 2); + size_type i = 0; + for (size_type g = 0; g < generators_.size(); ++g) { + out[i] = generators_[g]; + out[i + 1] = g; + i += 2; + } + return Multi_parameter_filtration(std::move(out), 2); + } + + /** + * @brief Converts the filtration value to @ref Dynamic_multi_parameter_filtration without any set simplification. + * @warning The filtration value is converted one to one and is not simplified to a minimal set of generators or + * ordered by lexicographical order, that undefines the behaviour of some methods of the class. + * Use @ref as_type(const Degree_rips_bifiltration&) instead if needed. + */ + Dynamic_multi_parameter_filtration + convert_to_non_simplified_dynamic_multi_parameter_filtration() const + { + std::vector > out; + out.reserve(generators_.size()); + for (size_type g = 0; g < generators_.size(); ++g) { + std::vector v = {generators_[g], static_cast(g)}; + out.emplace_back(std::move(v)); + } + return Dynamic_multi_parameter_filtration(std::move(out), 2); + } + + // ACCESS + + /** + * @brief Returns the number of parameters in the filtration value. + */ + static constexpr size_type num_parameters() { return 2; } + + /** + * @brief Returns the number of generators in the filtration value, i.e. the criticality of the element. + */ + size_type num_generators() const { return generators_.size(); } + + /** + * @brief Returns the total number of values in the filtration value, that is, + * @ref num_parameters() * @ref num_generators(). + */ + size_type num_entries() const { return generators_.size() * 2; } + + /** + * @brief Returns a filtration value for which @ref is_plus_inf() returns `true`. Throws if `Co` is true. + */ + static Degree_rips_bifiltration inf(int number_of_parameters = 2) + { + if constexpr (Co) { + throw std::logic_error("No biggest value possible for Co-filtrations yet."); + } else { + return Degree_rips_bifiltration(number_of_parameters, T_inf); + } + } + + /** + * @brief Returns a filtration value for which @ref is_minus_inf() returns `true`. + */ + static Degree_rips_bifiltration minus_inf(int number_of_parameters = 2) + { + return Degree_rips_bifiltration(number_of_parameters, T_m_inf); + } + + /** + * @brief Returns a filtration value for which @ref is_nan() returns `true`. + */ + static constexpr Degree_rips_bifiltration nan([[maybe_unused]] int number_of_parameters = 2) + { + return Degree_rips_bifiltration(Gudhi::simplex_tree::empty_filtration_value_t()); + } + + // DESCRIPTORS + + /** + * @brief Returns value of `Ensure1Criticality`. + */ + static constexpr bool ensures_1_criticality() { return Ensure1Criticality; } + + /** + * @brief Returns value of `Co`. + */ + static constexpr bool has_negative_cones() { return Co; } + + /** + * @brief Returns `true` if and only if the filtration value is considered as plus infinity. + */ + constexpr bool is_plus_inf() const + { + if constexpr (Co) { + return false; + } else { + if (generators_.empty()) return false; + for (const T &v : generators_) { + if (v != T_inf) return false; + } + return true; + } + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as minus infinity. + */ + constexpr bool is_minus_inf() const + { + if constexpr (Co) { + return generators_.size() == 1 && generators_[0] == T_m_inf; + } else { + return !generators_.empty() && generators_[0] == T_m_inf; + } + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as NaN. + */ + constexpr bool is_nan() const { return generators_.empty(); } + + /** + * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as plus infinity, + * minus infinity or NaN. + */ + bool is_finite() const + { + if constexpr (Co) { + return !generators_.empty() && (generators_.size() != 1 || generators_[0] != T_m_inf); + } else { + if (generators_.empty() || generators_[0] == T_m_inf) return false; + for (const T &v : generators_) { + if (v != T_inf) return true; + } + return false; + } + } + + // COMPARAISON OPERATORS + + /** + * @brief Returns `true` if and only if the first argument is lexicographically strictly less than the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator< "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_strict_less_than_lexicographically(const Degree_rips_bifiltration &a, + const Degree_rips_bifiltration &b) + { + if (&a == &b) return false; + if (a.is_nan()) return false; + if (b.is_nan()) return true; + + if constexpr (inverse) { + if (a.num_generators() != b.num_generators()) { + if (a.num_generators() == 0) return true; + if (b.num_generators() == 0) return false; + if (a.generators_[0] < b.generators_[0]) return true; + if (b.generators_[0] < a.generators_[0]) return false; + return a.num_generators() < b.num_generators(); + } + } + + for (std::size_t i = 0U; i < std::min(a.num_generators(), b.num_generators()); ++i) { + if constexpr (inverse) i = std::min(a.num_generators(), b.num_generators()) - 1 - i; + if (_is_nan(a.generators_[i]) && !_is_nan(b.generators_[i])) return false; + if (_is_nan(b.generators_[i])) return true; + if (a.generators_[i] < b.generators_[i]) return true; + if (b.generators_[i] < a.generators_[i]) return false; + if constexpr (inverse) i = std::min(a.num_generators(), b.num_generators()) - 1 - i; + } + return a.num_generators() < b.num_generators(); + } + + /** + * @brief Returns `true` if and only if the first argument is lexicographically less than or equal to the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator<= "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_less_or_equal_than_lexicographically(const Degree_rips_bifiltration &a, + const Degree_rips_bifiltration &b) + { + if (&a == &b) return true; + if (b.is_nan()) return true; + if (a.is_nan()) return false; + + if constexpr (inverse) { + if (a.num_generators() != b.num_generators()) { + if (a.num_generators() == 0) return true; + if (b.num_generators() == 0) return false; + if (a.generators_[0] < b.generators_[0]) return true; + if (b.generators_[0] < a.generators_[0]) return false; + return a.num_generators() < b.num_generators(); + } + } + + for (std::size_t i = 0U; i < std::min(a.num_generators(), b.num_generators()); ++i) { + if constexpr (inverse) i = std::min(a.num_generators(), b.num_generators()) - 1 - i; + if (_is_nan(a.generators_[i]) && !_is_nan(b.generators_[i])) return false; + if (_is_nan(b.generators_[i])) return true; + if (a.generators_[i] < b.generators_[i]) return true; + if (b.generators_[i] < a.generators_[i]) return false; + if constexpr (inverse) i = std::min(a.num_generators(), b.num_generators()) - 1 - i; + } + return a.num_generators() <= b.num_generators(); + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are strictly contained in the + * cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * + * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator<(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) + { + if (&a == &b) return false; + if (a.generators_.size() == 0 || b.generators_.size() == 0) return false; + return _compare_strict(0, a.generators_, b.generators_, a.generators_[0]); + } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are strictly contained in the + * cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * + * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator<=(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) + { + if (a.generators_.size() == 0 || b.generators_.size() == 0) return false; + if (&a == &b) return true; + return _compare(0, a.generators_, b.generators_, a.generators_[0]); + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are contained in or are (partially) + * equal to the cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * + * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator>(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) { return b < a; } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are contained in or are (partially) + * equal to the cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * + * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator>=(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) { return b <= a; } + + /** + * @brief Returns `true` if and only if for each \f$ i,j \f$, \f$ a(i,j) \f$ is equal to \f$ b(i,j) \f$. + * + * @warning The method considers different two filtration values with different generators, but as the set of + * generators is rarely minimal, it is still possible that those two values are equivalent. + */ + friend bool operator==(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) + { + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + return a.generators_ == b.generators_; + } + + /** + * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. + */ + friend bool operator!=(const Degree_rips_bifiltration &a, const Degree_rips_bifiltration &b) { return !(a == b); } + + // ARITHMETIC OPERATORS + + // opposite + /** + * @brief Returns a filtration value such that an entry at index \f$ i,0 \f$ is equal to \f$ -f(i,0) \f$. + * + * Used conventions: + * - \f$ -NaN = NaN \f$. + * + * @param f Value to opposite. + * @return The opposite of @p f. + */ + friend Degree_rips_bifiltration operator-(const Degree_rips_bifiltration &f) + { + using F = Degree_rips_bifiltration; + + Underlying_container result(f.generators_); + std::for_each(result.begin(), result.end(), [](T &v) { + if (v == F::T_inf) + v = F::T_m_inf; + else if (v == F::T_m_inf) + v = F::T_inf; + else + v = -v; + }); + return Degree_rips_bifiltration(std::move(result), Degree_rips_bifiltration::num_parameters()); + } + + // subtraction + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) - r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Degree_rips_bifiltration operator-(Degree_rips_bifiltration f, const ValueRange &r) + { + f -= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ r(0) - f(g,0) \f$ + * if \f$ 0 < length_r \f$ and to -\f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the subtraction. + * @param f Second element of the subtraction. + */ + template ::has_begin && + !std::is_same_v > > + friend Degree_rips_bifiltration operator-(const ValueRange &r, Degree_rips_bifiltration f) + { + if (r.begin() == r.end()) return -f; + return *(r.begin()) - f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Degree_rips_bifiltration operator-(Degree_rips_bifiltration f, const T &val) + { + f -= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ val - f(g,0) \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the subtraction. + * @param f Second element of the subtraction. + */ + friend Degree_rips_bifiltration operator-(const T &val, Degree_rips_bifiltration f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + valF = -valF; + _add(valF, valR); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) - r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Degree_rips_bifiltration &operator-=(Degree_rips_bifiltration &f, const ValueRange &r) + { + if (r.begin() == r.end()) return f; + f -= *(r.begin()); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Degree_rips_bifiltration &operator-=(Degree_rips_bifiltration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _subtract(valF, valR); }); + return f; + } + + // addition + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) + r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Degree_rips_bifiltration operator+(Degree_rips_bifiltration f, const ValueRange &r) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ r(0) + f(g,0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the addition. + * @param f Second element of the addition. + */ + template ::has_begin && + !std::is_same_v > > + friend Degree_rips_bifiltration operator+(const ValueRange &r, Degree_rips_bifiltration f) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Degree_rips_bifiltration operator+(Degree_rips_bifiltration f, const T &val) + { + f += val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ val + f(g,0) \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the addition. + * @param f Second element of the addition. + */ + friend Degree_rips_bifiltration operator+(const T &val, Degree_rips_bifiltration f) + { + f += val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) + r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Degree_rips_bifiltration &operator+=(Degree_rips_bifiltration &f, const ValueRange &r) + { + if (r.begin() == r.end()) return f; + f += *(r.begin()); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Degree_rips_bifiltration &operator+=(Degree_rips_bifiltration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _add(valF, valR); }); + return f; + } + + // multiplication + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) * r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Degree_rips_bifiltration operator*(Degree_rips_bifiltration f, const ValueRange &r) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ r(0) * f(g,0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the multiplication. + * @param f Second element of the multiplication. + */ + template ::has_begin && + !std::is_same_v > > + friend Degree_rips_bifiltration operator*(const ValueRange &r, Degree_rips_bifiltration f) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Degree_rips_bifiltration operator*(Degree_rips_bifiltration f, const T &val) + { + f *= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ val * f(g,0) \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the multiplication. + * @param f Second element of the multiplication. + */ + friend Degree_rips_bifiltration operator*(const T &val, Degree_rips_bifiltration f) + { + f *= val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) + r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Degree_rips_bifiltration &operator*=(Degree_rips_bifiltration &f, const ValueRange &r) + { + if (r.begin() == r.end()) return f; + f *= *(r.begin()); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Degree_rips_bifiltration &operator*=(Degree_rips_bifiltration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _multiply(valF, valR); }); + return f; + } + + // division + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) / r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Degree_rips_bifiltration operator/(Degree_rips_bifiltration f, const ValueRange &r) + { + f /= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ r(0) / f(g,0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ 1 / f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the division. + * @param f Second element of the division. + */ + template ::has_begin && + !std::is_same_v > > + friend Degree_rips_bifiltration operator/(const ValueRange &r, Degree_rips_bifiltration f) + { + if (r.begin() == r.end()) return 1 / f; + return *(r.begin()) / f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Degree_rips_bifiltration operator/(Degree_rips_bifiltration f, const T &val) + { + f /= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,0) \f$ is equal to \f$ val / f(g,0) \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the division. + * @param f Second element of the division. + */ + friend Degree_rips_bifiltration operator/(const T &val, Degree_rips_bifiltration f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + T tmp = valF; + valF = valR; + _divide(valF, tmp); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ is equal to \f$ f(g,0) / r(0) \f$ + * if \f$ 0 < length_r \f$ and to \f$ f(g,0) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Degree_rips_bifiltration &operator/=(Degree_rips_bifiltration &f, const ValueRange &r) + { + if (r.begin() == r.end()) return f; + f /= *(r.begin()); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,0) \f$ is equal to \f$ f(g,0) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Degree_rips_bifiltration &operator/=(Degree_rips_bifiltration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _divide(valF, valR); }); + return f; + } + + // MODIFIERS + + /** + * @brief Sets the number of generators. If there were less generators before, new generators with default values are + * constructed. If there were more generators before, the exceed of generators is destroyed (any generator with index + * higher or equal to @p g to be more precise). If @p g is zero, the methods does nothing. + * + * Fails to compile if `Ensure1Criticality` is true. + * + * @param g New number of generators. + */ + void set_num_generators(size_type g) + { + static_assert(!Ensure1Criticality, "Number of generators cannot be set for a 1-critical only filtration value."); + + if (g == 0) return; + generators_.resize(g, _get_default_value()); + } + + /** + * @brief Adds the given generator to the filtration value. + * + * It is possible that the generator is ignored if the first parameter is overshadowed by an already existing + * generator with same second parameter. This would mean that adding the given generator will not span more + * "lifetime" and therefore there is no need to store it. + * + * Let \f$ max_idx \$f be the highest second parameter stored so far. If the given second parameter \f$ i \$f to add + * is strictly higher than \f$ max_idx + 1 \$f, all possible values between \f$ max_idx \$f and \f$ i \$f will also + * be added and the corresponding first parameters will be initialized with -inf if `Co` is false and with +inf + * if `Co` is true. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() method and the iterator + * type should satisfy the requirements of the standard `LegacyForwardIterator`. + * @param x New generator to add. Has to have the 2 parameters. + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool add_generator(const GeneratorRange &x) + { + return add_generator(x.begin(), x.end()); + } + + /** + * @brief Adds the given generator to the filtration value. + * + * It is possible that the generator is ignored if the first parameter is overshadowed by an already existing + * generator with same second parameter. This would mean that adding the given generator will not span more + * "lifetime" and therefore there is no need to store it. + * + * Let \f$ max_idx \$f be the highest second parameter stored so far. If the given second parameter \f$ i \$f to add + * is strictly higher than \f$ max_idx + 1 \$f, all possible values between \f$ max_idx \$f and \f$ i \$f will also + * be added and the corresponding first parameters will be initialized with inf if `Co` is false and with -inf + * if `Co` is true. + * + * @tparam Iterator Iterator class satisfying the requirements of the standard `LegacyForwardIterator`. + * The dereferenced type has to be convertible to `T`. + * @param genStart Iterator pointing to the begining of the range of two elements. + * @param genEnd Iterator pointing to the end of the range. + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template + bool add_generator(Iterator genStart, [[maybe_unused]] Iterator genEnd) + { + GUDHI_CHECK(std::distance(genStart, genEnd) == 2, + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + const T val = *genStart; + ++genStart; + const size_type index = *genStart; + + GUDHI_CHECK(index >= 0, std::invalid_argument("Second parameter has to be a positive index.")); + + if (_is_nan(val)) return false; + + if (index < generators_.size()) { + if (_dominates(val, generators_[index])) return false; + generators_[index] = val; + return true; + } + + if constexpr (Ensure1Criticality) { + if (index != 0) throw std::logic_error("Cannot add additional generator to a 1-critical only filtration value."); + } + + generators_.resize(index + 1, _get_default_minus_value()); + generators_[index] = val; + return true; + } + + /** + * @brief Same as @ref add_generator "". + */ + template , + class = std::enable_if_t::has_begin> > + void add_guaranteed_generator(const GeneratorRange &x) + { + add_generator(x.begin(), x.end()); + } + + /** + * @brief Does nothing, only for interface purposes. + */ + void simplify() {} + + /** + * @brief Does nothing, only for interface purposes. + */ + void remove_empty_generators([[maybe_unused]] bool include_infinities = false) {} + + /** + * @brief Let \f$ g_c \f$ be the positive cone generated by the generator \f$ g \f$ of the filtration value. And let + * \f$ x_c \f$ be the positive cone generated by the given point \f$ x \f$. For each generator \f$ g \f$ of the + * filtration value such that \f$ g_c \f$ strictly contains \f$ x_c \f$, pushes it to the border of \f$ x_c \f$. + * + * Say \f$ x = (val, idx) \f$. The second parameter \f$ idx \f$ is assumed to be positive. + * Note also that, if `Co` is true, the filtration value cannot be pushed to \f$ (*,inf) \f$, and that, + * if `Ensure1Criticality` is true, \f$ idx \f$ has to be \f$ 0 \f$. + * + * @tparam GeneratorRange Either a range of into `T` convertible elements with a begin(), end() and size() method, + * or @ref Degree_rips_bifiltration with `U` convertible into `T`. + * @param x Range towards to push. Has to have 2 elements (all other elements above that count are ignored). If the + * range is a @ref Degree_rips_bifiltration, the generator at second parameter 0 is chosen. + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool push_to_least_common_upper_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + if (is_nan()) return false; + + size_type start = 0; + T startVal = 0; + T xVal; + + if constexpr (RangeTraits::is_multi_filtration) { + if (x.size() == 0) return false; // nan + xVal = x(0,0); + } else { + GUDHI_CHECK(x.size() == 2, + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + auto it = x.begin(); + xVal = *it; + ++it; + startVal = *it; + if (startVal < 0) throw std::invalid_argument("Second parameter has to be a positive integer."); + } + + if (startVal == T_inf) { + if constexpr (Co) { + throw std::invalid_argument("Filtration values with negative cones cannot be pushed to (*,inf)."); + } + if (is_plus_inf()) return false; + // not exactly true, but the closest feasible representation + *this = inf(); + return true; + } + start = startVal; + if ((start == 0 && xVal == T_m_inf) || _is_nan(xVal)) return false; + + if constexpr (Ensure1Criticality) { + // TODO: we could at some point allow [(inf,0),...,(inf,n-1),(v,n)] as 1-critical, but it would make everything + // more complicated and this class does not seem to be the right option to handle those cases anyway... + if (start != 0) + throw std::invalid_argument( + "Pushing to an index other than 0 or inf is not permitted with Ensure1Criticality at true."); + } + + bool modified = false; + T newVal = _get_default_minus_value(); + + for (size_type i = 0; i < std::min(start, generators_.size()); ++i){ + auto& v = generators_[i]; + if (!_is_nan(v) && (!exclude_infinite_values || (v != T_inf && v != T_m_inf))){ + T threshold = std::max(v, xVal); + if (_dominates(newVal, threshold)) newVal = threshold; + if (v != _get_default_minus_value()) { + modified = true; + v = _get_default_minus_value(); + } + } + } + + if (start < generators_.size()){ + auto& vs = generators_[start]; + T threshold = std::max(vs, xVal); + if (_dominates(newVal, threshold)) newVal = threshold; + if (newVal != vs) { + modified = true; + vs = newVal; + } + + for (size_type i = start + 1; i < generators_.size(); ++i) { + auto& v = generators_[i]; + if (!_is_nan(v) && v < xVal && (!exclude_infinite_values || (v != T_inf && v != T_m_inf))) { + modified = true; + v = xVal; + } + } + } else { + modified = true; + generators_.resize(start + 1, _get_default_minus_value()); + generators_[start] = newVal; + } + + if (is_plus_inf()) *this = inf(); + + return modified; + } + + /** + * @brief Let \f$ g_c \f$ be the negative cone generated by the generator \f$ g \f$ of the filtration value. And let + * \f$ x_c \f$ be the negative cone generated by the given point \f$ x \f$. For each generator \f$ g \f$ of the + * filtration value such that \f$ g_c \f$ strictly contains \f$ x_c \f$, pulls it to the border of \f$ x_c \f$. + * + * Say \f$ x = (val, idx) \f$. The second parameter \f$ idx \f$ is assumed to be positive. + * + * @tparam GeneratorRange Either a range of into `T` convertible elements with a begin(), end() and size() method, + * or @ref Degree_rips_bifiltration with `U` convertible into `T`. + * @param x Range towards to push. Has to have 2 elements (all other elements above that count are ignored). If the + * range is a @ref Degree_rips_bifiltration, the generator at second parameter 0 is chosen. + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool pull_to_greatest_common_lower_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + if (is_nan()) return false; + + size_type end; + T endVal = 0; + T xVal; + + if constexpr (RangeTraits::is_multi_filtration) { + if (x.size() == 0) return false; // nan + xVal = x(0,0); + } else { + GUDHI_CHECK(x.size() == 2, + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + auto it = x.begin(); + xVal = *it; + ++it; + endVal = *it; + } + + if (endVal < 0) throw std::invalid_argument("Second parameter has to be a positive integer."); + if (endVal == T_inf) end = generators_.size(); + else end = endVal; + + if ((end >= generators_.size() - 1 && xVal == T_inf) || _is_nan(xVal)) return false; + + bool modified = false; + T newVal = _get_default_minus_value(); + + for (size_type i = 0; i < std::min(end, generators_.size()); ++i) { + auto &v = generators_[i]; + if (!_is_nan(v) && v > xVal && (!exclude_infinite_values || (v != T_inf && v != T_m_inf))) { + modified = true; + v = xVal; + } + } + + if (end < generators_.size()) { + for (size_type i = end; i < generators_.size(); ++i) { + auto v = generators_[i]; + if (!_is_nan(v) && (!exclude_infinite_values || (v != T_inf && v != T_m_inf))){ + T threshold = std::min(v, xVal); + if (_dominates(newVal, threshold)) newVal = threshold; + } + } + + modified |= (end + 1) != generators_.size(); + generators_.resize(end + 1); + + modified |= generators_[end] != newVal; + generators_[end] = newVal; + } + + return modified; + } + + /** + * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. The range of the second parameter has to start at 0 and continue continuously. + * + * @tparam OneDimArray A range of values convertible into `T` ordered by increasing value. Has to implement + * a begin, end and operator[] method. + * @param grid Vector of @p OneDimArray with size at least 2. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. + */ + template + void project_onto_grid(const std::vector &grid, bool coordinate = true) + { + GUDHI_CHECK( + grid.size() >= 2, + std::invalid_argument("The grid should not be smaller than the number of parameters in the filtration value.")); + + GUDHI_CHECK_code(const OneDimArray &indices = grid[1]); + const OneDimArray &values = grid[0]; + for (size_type g = 0; g < num_generators(); ++g) { + GUDHI_CHECK_code(GUDHI_CHECK(static_cast(indices[g]) == g, std::invalid_argument("Unvalid grid."))); + + auto d = std::distance( + values.begin(), + std::lower_bound( + values.begin(), values.end(), static_cast(generators_[g]))); + generators_[g] = coordinate ? static_cast(d) : static_cast(values[d]); + } + } + + // FONCTIONNALITIES + + /** + * @brief Returns the filtration value that is the greatest lower bound of all generators. + */ + friend Degree_rips_bifiltration factorize_below(const Degree_rips_bifiltration &f) + { + if (f.num_generators() <= 1) return f; + + bool nan = true; + size_type idx = f.num_generators(); + T val = T_inf; + + if constexpr (Co) { + // -inf are "non existing" generators if not all of them are at -inf + bool inf = true; + for (size_type g = 0; g < f.num_generators(); ++g) { + T v = f.generators_[g]; + if (!_is_nan(v) && v != T_m_inf) { + nan = false; + inf = false; + val = v < val ? v : val; + idx = g < idx ? g : idx; + } else if (_is_nan(v)) { + inf = false; + } else { + nan = false; + } + } + if (inf) return Degree_rips_bifiltration::minus_inf(); + } else { + idx = 0; + for (const T &v : f.generators_) { + if (!_is_nan(v)) { + nan = false; + val = v < val ? v : val; + } + } + } + + if (nan) return Degree_rips_bifiltration::nan(); + + Underlying_container result(idx + 1, _get_default_minus_value()); + result[idx] = val; + return Degree_rips_bifiltration(std::move(result), 2); + } + + /** + * @brief Returns the filtration value that is the least upper bound of all generators. + */ + friend Degree_rips_bifiltration factorize_above(const Degree_rips_bifiltration &f) + { + if (f.num_generators() <= 1) return f; + + bool nan = true; + size_type idx = 0; + T val = T_m_inf; + + if constexpr (Co) { + idx = f.num_generators() - 1; + for (const T &v : f.generators_) { + if (!_is_nan(v)) { + nan = false; + val = v > val ? v : val; + } + } + } else { + // inf are "non existing" generators if not all of them are at inf + bool inf = true; + for (size_type g = 0; g < f.num_generators(); ++g) { + T v = f.generators_[g]; + if (!_is_nan(v) && v != T_inf) { + nan = false; + inf = false; + val = v > val ? v : val; + idx = g > idx ? g : idx; + } else if (_is_nan(v)) { + inf = false; + } else { + nan = false; + } + } + if (inf) return Degree_rips_bifiltration::inf(); + } + + if (nan) return Degree_rips_bifiltration::nan(); + + Underlying_container result(idx + 1, _get_default_minus_value()); + result[idx] = val; + return Degree_rips_bifiltration(std::move(result), 2); + } + + /** + * @brief Computes the smallest (resp. the greatest if `Co` is true) scalar product of the all generators with the + * given vector. + * + * @tparam U Arithmetic type of the result. Default value: `T`. + * @param f Filtration value. + * @param x Vector of coefficients. + * @return Scalar product of @p f with @p x. + */ + template + friend U compute_linear_projection(const Degree_rips_bifiltration &f, const std::vector &x) + { + auto project_generator = [&](size_type g) -> U { + U projection = 0; + std::size_t size = std::min(x.size(), Degree_rips_bifiltration::num_parameters()); + for (std::size_t i = 0; i < size; i++) projection += x[i] * static_cast(f(g, i)); + return projection; + }; + + if (f.num_generators() == 1) return project_generator(0); + + if constexpr (Co) { + U projection = std::numeric_limits::lowest(); + for (size_type g = 0; g < f.num_generators(); ++g) { + // Order in the max important to spread possible NaNs + projection = std::max(project_generator(g), projection); + } + return projection; + } else { + U projection = std::numeric_limits::max(); + for (size_type g = 0; g < f.num_generators(); ++g) { + // Order in the min important to spread possible NaNs + projection = std::min(project_generator(g), projection); + } + return projection; + } + } + + /** + * @brief Computes the euclidean distance from the first parameter to the second parameter as the minimum of + * all Euclidean distances between a generator of @p f and a generator of @p other. + * + * @param f Source filtration value. + * @param other Target filtration value. + * @return Euclidean distance between @p f and @p other. + */ + template + friend U compute_euclidean_distance_to(const Degree_rips_bifiltration &f, const Degree_rips_bifiltration &other) + { + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + return _compute_frobenius_norm(Degree_rips_bifiltration::num_parameters(), + [&](size_type p) -> T { return f(0, p) - other(0, p); }); + } else { + U res = std::numeric_limits::max(); + for (size_type g1 = 0; g1 < f.num_generators(); ++g1) { + for (size_type g2 = 0; g2 < other.num_generators(); ++g2) { + // Euclidean distance as a Frobenius norm with matrix 1 x p and values 'f(g1, p) - other(g2, p)' + // Order in the min important to spread possible NaNs + res = std::min(_compute_frobenius_norm(Degree_rips_bifiltration::num_parameters(), + [&](size_type p) -> T { return f(g1, p) - other(g2, p); }), + res); + } + } + return res; + } + } + + /** + * @brief Computes the norm of the given filtration value. + * + * The filtration value is seen as a \f$ num_generators x num_parameters \f$ matrix and a standard Frobenius norm + * is computed from it: the square root of the sum of the squares of all elements in the matrix. + * + * @param f Filtration value. + * @return The norm of @p f. + */ + template + friend U compute_norm(const Degree_rips_bifiltration &f) + { + // Frobenius norm with matrix g x p based on Euclidean norm + + if (f.num_generators() == 1) return f.generators_[0]; + + U out = 0; + for (size_type g = 0; g < f.num_generators(); ++g) { + out += g * g; + out += f.generators_[g] * f.generators_[g]; + } + + if constexpr (std::is_integral_v) { + // to avoid Windows issue that don't know how to cast integers for cmath methods + return std::sqrt(static_cast(out)); + } else { + return std::sqrt(out); + } + } + + /** + * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries + * in the given filtration value. + * The grid has to be represented as a vector of vectors of ordered values convertible into `OutValue`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. The range of the second parameter has to start at 0 and continue continuously. + * + * @tparam OutValue Signed arithmetic type. Default value: std::int32_t. + * @tparam U Type which is convertible into `OutValue`. + * @param f Filtration value to project. + * @param grid Vector of vectors to project into. + * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is, + * the projection of \f$ f(g,p) \f$ is \f$ grid[p][out(g,p)] \f$. + */ + template + friend Degree_rips_bifiltration compute_coordinates_in_grid( + Degree_rips_bifiltration f, + const std::vector > &grid) + { + // TODO: by replicating the code of "project_onto_grid", this could be done with just one copy + // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really + // necessary in the use cases. To see later. + f.project_onto_grid(grid); + if constexpr (std::is_same_v) { + return f; + } else { + return f.as_type(); + } + } + + /** + * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration + * value. That is, if \f$ out \f$ is the result, \f$ out(g,p) = grid[p][f(g,p)] \f$. Assumes therefore, that the + * values stored in the filtration value corresponds to indices existing in the given grid. + * + * @tparam U Signed arithmetic type. + * @param f Filtration value storing coordinates compatible with `grid`. + * @param grid Vector of vector. + * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out(g,p) = grid[p][f(g,p)] \f$. + */ + template + friend Degree_rips_bifiltration evaluate_coordinates_in_grid( + const Degree_rips_bifiltration &f, + const std::vector > &grid) + { + GUDHI_CHECK(grid.size() >= f.num_parameters(), + std::invalid_argument( + "The size of the grid should correspond to the number of parameters in the filtration value.")); + + U grid_inf = Degree_rips_bifiltration::T_inf; + std::vector outVec(f.num_generators()); + + GUDHI_CHECK_code(const std::vector &indices = grid[1]); + const std::vector &values = grid[0]; + for (size_type g = 0; g < f.num_generators(); ++g) { + GUDHI_CHECK_code(GUDHI_CHECK(static_cast(indices[g]) == g, std::invalid_argument("Unvalid grid."))); + + const T &c = f.generators_[g]; + outVec[g] = (c == T_inf ? grid_inf : values[c]); + } + + return Degree_rips_bifiltration(std::move(outVec), + Degree_rips_bifiltration::num_parameters()); + } + + // UTILITIES + + /** + * @brief Outstream operator. + */ + friend std::ostream &operator<<(std::ostream &stream, const Degree_rips_bifiltration &f) + { + const size_type num_gen = f.num_generators(); + const size_type num_param = Degree_rips_bifiltration::num_parameters(); + + stream << "( k = " << num_gen << " ) [ "; + for (size_type g = 0; g < num_gen; ++g) { + stream << "["; + for (size_type p = 0; p < num_param; ++p) { + stream << f(g, p); + if (p < num_param - 1) stream << ", "; + } + stream << "]"; + if (g < num_gen - 1) stream << "; "; + } + stream << " ]"; + + return stream; + } + + /** + * @brief Instream operator. + */ + friend std::istream &operator>>(std::istream &stream, Degree_rips_bifiltration &f) + { + size_type num_gen; + char delimiter; + stream >> delimiter; // ( + stream >> delimiter; // k + stream >> delimiter; // = + if (delimiter != '=') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + stream >> num_gen; + if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + f.generators_.resize(num_gen); + stream >> delimiter; // ) + stream >> delimiter; // [ + if (delimiter != '[') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + if (num_gen == 0) return stream; + T val; + for (size_type i = 0; i < num_gen; ++i) { + stream >> delimiter; // [ + val = _get_value(stream); + f.generators_[i] = val; + stream >> delimiter; // , + stream >> val; + if (val != static_cast(i)) + throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + stream >> delimiter; // ] + stream >> delimiter; // ; or last ] + } + if (delimiter != ']') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + + return stream; + } + + /** + * @brief Returns true if and only if the given filtration value is at plus infinity. + */ + friend bool is_positive_infinity(const Degree_rips_bifiltration &f) + { + return f.is_plus_inf(); + } + + /** + * @brief Adds the generators of the second argument to the first argument. + * + * @param f1 Filtration value to modify. + * @param f2 Filtration value to merge with the first one. Should have the same number of parameters than the other. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool unify_lifetimes(Degree_rips_bifiltration &f1, const Degree_rips_bifiltration &f2) + { + bool modified = false; + for (size_type g = 0; g < f2.num_generators(); ++g) { + modified |= f1.add_generator({f2.generators_[g], static_cast(g)}); + } + return modified; + } + + /** + * @brief Stores in the first argument the origins of the cones in the intersection of the positive + * (negative if `Co` is true) cones generated by the two arguments. + * + * @param f1 First set of cones which will be modified. + * @param f2 Second set of cones. Should have the same number of parameters than the first one. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool intersect_lifetimes(Degree_rips_bifiltration &f1, const Degree_rips_bifiltration &f2) + { + if (f2.num_generators() == 0 || f1.num_generators() == 0) { + Degree_rips_bifiltration res(2, _get_default_minus_value()); + swap(f1, res); + return f1 != res; + } + + bool modified = false; + T threshold1 = f1.generators_[0]; + T threshold2 = f2.generators_[0]; + for (size_type g = 0; g < std::max(f1.num_generators(), f2.num_generators()); ++g) { + if (g < f1.num_generators()) + threshold1 = _strictly_dominates(threshold1, f1.generators_[g]) ? f1.generators_[g] : threshold1; + else { + f1.generators_.push_back(0); + modified = true; + } + if (g < f2.num_generators()) + threshold2 = _strictly_dominates(threshold2, f2.generators_[g]) ? f2.generators_[g] : threshold2; + if (_strictly_dominates(threshold2, threshold1)) { + if (f1.generators_[g] != threshold2) modified = true; + f1.generators_[g] = threshold2; + } else { + if (f1.generators_[g] != threshold1) modified = true; + f1.generators_[g] = threshold1; + } + } + + return modified; + } + + /** + * @brief Serialize given value into the buffer at given pointer. + * + * @param value Value to serialize. + * @param start Pointer to the start of the space in the buffer where to store the serialization. + * @return End position of the serialization in the buffer. + */ + friend char *serialize_value_to_char_buffer(const Degree_rips_bifiltration &value, char *start) + { + const size_type length = value.generators_.size(); + const std::size_t arg_size = sizeof(T) * length; + const std::size_t type_size = sizeof(size_type); + memcpy(start, &length, type_size); + memcpy(start + type_size, value.generators_.data(), arg_size); + return start + arg_size + type_size; + } + + /** + * @brief Deserialize the value from a buffer at given pointer and stores it in given value. + * + * @param value Value to fill with the deserialized filtration value. + * @param start Pointer to the start of the space in the buffer where the serialization is stored. + * @return End position of the serialization in the buffer. + */ + friend const char *deserialize_value_from_char_buffer(Degree_rips_bifiltration &value, const char *start) + { + const std::size_t type_size = sizeof(size_type); + size_type length; + memcpy(&length, start, type_size); + std::size_t arg_size = sizeof(T) * length; + value.generators_.resize(length); + memcpy(value.generators_.data(), start + type_size, arg_size); + return start + arg_size + type_size; + } + + /** + * @brief Returns the serialization size of the given filtration value. + */ + friend std::size_t get_serialization_size_of(const Degree_rips_bifiltration &value) + { + return sizeof(size_type) + (sizeof(T) * value.generators_.size()); + } + + /** + * @brief Plus infinity value of an entry of the filtration value. + */ + constexpr static const T T_inf = MF_T_inf; + + /** + * @brief Minus infinity value of an entry of the filtration value. + */ + constexpr static const T T_m_inf = MF_T_m_inf; + + private: + Underlying_container generators_; /**< Container of the filtration value elements. */ + mutable T dummy_g_; /**< Dummy value for the first parameter, such that a reference can be returned. */ + + /** + * @brief Default value of an element in the filtration value. + */ + constexpr static T _get_default_value() { return Co ? T_inf : T_m_inf; } + constexpr static T _get_default_minus_value() { return Co ? T_m_inf : T_inf; } + + // < + static bool _compare_strict(size_type i, const Underlying_container &a, const Underlying_container &b, T threshold) + { + if (i >= b.size()) return true; + if (_is_nan(b[i])) return false; + + if (i >= a.size() && _strictly_dominates(threshold, b[i])) return false; + if (i < a.size()) { + if (_is_nan(a[i])) return false; + threshold = _strictly_dominates(threshold, a[i]) ? a[i] : threshold; + if (a[i] == threshold && b[i] == threshold) return false; + } + if (_strictly_dominates(threshold, b[i])) return false; + + return _compare_strict(i + 1, a, b, threshold); + } + + // <= + static bool _compare(size_type i, const Underlying_container &a, const Underlying_container &b, T threshold) + { + if (i >= b.size()) return true; + if (_is_nan(b[i])) return false; + + if (i >= a.size() && _strictly_dominates(threshold, b[i])) return false; + if (i < a.size()) { + if (_is_nan(a[i])) return false; + threshold = _strictly_dominates(threshold, a[i]) ? a[i] : threshold; + } + if (_strictly_dominates(threshold, b[i])) return false; + + return _compare(i + 1, a, b, threshold); + } + + static bool _strictly_dominates(T a, T b) + { + if constexpr (Co) { + return a < b; + } else { + return a > b; + } + } + + static bool _dominates(T a, T b) + { + if constexpr (Co) { + return a <= b; + } else { + return a >= b; + } + } + + /** + * @brief Applies operation on the elements of the filtration value. + */ + template + void _apply_operation(const T &val, F &&operate) + { + auto &gens = generators_; + for (unsigned int i = 0; i < gens.size(); ++i) { + std::forward(operate)(gens[i], val); + } + } + + template + static U _compute_frobenius_norm(size_type number_of_elements, F &&norm) + { + if (number_of_elements == 1) return norm(0); + + U out = 0; + for (size_type p = 0; p < number_of_elements; ++p) { + T v = std::forward(norm)(p); + out += v * v; + } + if constexpr (std::is_integral_v) { + // to avoid Windows issue that don't know how to cast integers for cmath methods + return std::sqrt(static_cast(out)); + } else { + return std::sqrt(out); + } + } +}; + +} // namespace Gudhi::multi_filtration + +namespace std { + +template +class numeric_limits > +{ + public: + using Filtration_value = Gudhi::multi_filtration::Degree_rips_bifiltration; + + static constexpr bool has_infinity = !Co; + static constexpr bool has_quiet_NaN = true; + + static constexpr Filtration_value infinity(std::size_t p = 2) noexcept(!Co) { return Filtration_value::inf(p); }; + + // non-standard + static constexpr Filtration_value minus_infinity(std::size_t p = 2) noexcept + { + return Filtration_value::minus_inf(p); + }; + + static constexpr Filtration_value max(std::size_t p = 2) noexcept(!Co) + { + if constexpr (Co) { + throw std::logic_error("No biggest value possible for Co-filtrations yet."); + } else { + return Filtration_value(p, std::numeric_limits::max()); + } + }; + + static constexpr Filtration_value lowest(std::size_t p = 2) noexcept { return Filtration_value::minus_inf(p); }; + + static constexpr Filtration_value quiet_NaN(std::size_t p = 2) noexcept { return Filtration_value::nan(p); }; +}; + +} // namespace std + +#endif // MF_DEGREE_RIPS_BIFILTRATION_H_ diff --git a/multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h b/multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h new file mode 100644 index 00000000..f0d49f37 --- /dev/null +++ b/multipers/gudhi/gudhi/Dynamic_multi_parameter_filtration.h @@ -0,0 +1,2516 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, David Loiseaux + * + * Copyright (C) 2024-25 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Dynamic_multi_parameter_filtration.h + * @author Hannah Schreiber, David Loiseaux + * @brief Contains the @ref Gudhi::multi_filtration::Dynamic_multi_parameter_filtration class. + */ + +#ifndef MF_DYNAMIC_MULTI_PARAMETER_FILTRATION_H_ +#define MF_DYNAMIC_MULTI_PARAMETER_FILTRATION_H_ + +#include //std::lower_bound +#include //std::isnan, std::min +#include //std::size_t +#include //std::int32_t, std::uint8_t +#include //memcpy +#include //std::distance +#include //std::ostream +#include //std::numerical_limits +#include //std::logic_error +#include //std::is_arithmetic +#include //std::swap, std::move +#include +#include + +#include +#include +#include + +namespace Gudhi::multi_filtration { + +/** + * @class Dynamic_multi_parameter_filtration Dynamic_multi_parameter_filtration.h + * gudhi/Dynamic_multi_parameter_filtration.h + * @ingroup multi_filtration + * + * @brief Class encoding the different generators, i.e., apparition times, of a \f$ k \f$-critical + * \f$\mathbb R^n\f$-filtration value. E.g., the filtration value of a simplex, or, of the algebraic generator of a + * module presentation. Different from @ref Multi_parameter_filtration, the underlying container is a vector of vectors + * and therefore less memory efficient, but much more flexible when modifying the filtration value. So, this class is + * preferable if a lot of generators need to be added on the fly or removed. When the filtration values are fixed or + * 1-critical, we recommend @ref Multi_parameter_filtration instead. Implements the concept @ref FiltrationValue of the + * @ref Gudhi::Simplex_tree and the concept @ref Gudhi::multi_persistence::MultiFiltrationValue. + * + * @details Overloads `std::numeric_limits` such that: + * - `std::numeric_limits::has_infinity` returns `true`, + * - `std::numeric_limits::has_quiet_NaN` returns `true`, + * - `std::numeric_limits::infinity(int)` returns + * @ref Dynamic_multi_parameter_filtration::inf(int) "", + * - `std::numeric_limits::minus_infinity(int)` returns + * @ref Dynamic_multi_parameter_filtration::minus_inf(int) "", + * - `std::numeric_limits::max(int num_param)` returns a @ref + * Dynamic_multi_parameter_filtration with one generator of `num_param` parameters evaluated at value + * `std::numeric_limits::max()`, + * - `std::numeric_limits::quiet_NaN(int)` returns + * @ref Dynamic_multi_parameter_filtration::nan(int). + * + * Multi-critical filtrations are filtrations such that the lifetime of each object is a union of positive cones in + * \f$\mathbb R^n\f$, e.g., + * - \f$ \{ x \in \mathbb R^2 : x \ge (1,2)\} \cap \{ x \in \mathbb R^2 : x \ge (2,1)\} \f$ is finitely critical, + * and more particularly 2-critical, while + * - \f$ \{ x \in \mathbb R^2 : x \ge \mathrm{epigraph}(y \mapsto e^{-y})\} \f$ is not. + * + * @tparam T Arithmetic type of an entry for one parameter of a filtration value. Has to be **signed** and + * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, + * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. + * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` + * can simply throw. Examples are the native types `double`, `float` and `int`. + * @tparam Co If `true`, reverses the poset order, i.e., the order \f$ \le \f$ in \f$ \mathbb R^n \f$ becomes + * \f$ \ge \f$. That is, the positive cones representing a lifetime become all negative instead. + * @tparam Ensure1Criticality If `true`, the methods ensure that the filtration value is always 1-critical by throwing + * or refusing to compile if a modification increases the number of generators. + */ +template +class Dynamic_multi_parameter_filtration +{ + public: + using Generator = Multi_parameter_generator; /**< Generator type. */ + using Underlying_container = std::vector; /**< Underlying container for values. */ + + // CONSTRUCTORS + + /** + * @brief Default constructor. Builds filtration value with one generator and given number of parameters. + * If Co is false, initializes at -inf, if Co is true, at +inf. + * + * @param number_of_parameters If negative, takes the default value instead. Default value: 2. + */ + Dynamic_multi_parameter_filtration(int number_of_parameters = 2) + : number_of_parameters_(number_of_parameters < 0 ? 2 : number_of_parameters), + generators_(1, Generator(1, Co ? T_inf : T_m_inf)) + {} + + /** + * @brief Builds filtration value with one generator and given number of parameters. + * All values are initialized at the given value. + * + * @warning The generator `{-inf, -inf, ...}`/`{inf, inf, ...}` with \f$ number_of_parameters > 1 \f$ entries is + * valid but will not benefit from possible optimizations. If those values are not planed to be replaced, it is + * recommended to use the static methods @ref minus_inf() or @ref inf(), or set `number_of_parameters` to 1, instead. + * + * @param number_of_parameters If negative, is set to 2 instead. + * @param value Initialization value for every value in the generator. + */ + Dynamic_multi_parameter_filtration(int number_of_parameters, T value) + : number_of_parameters_(number_of_parameters < 0 ? 2 : number_of_parameters), + generators_(1, Generator(number_of_parameters_, value)) + {} + + /** + * @brief Builds filtration value with one generator that is initialized with the given range. The number of + * parameters are therefore deduced from the length of the range. + * + * @tparam ValueRange Range of types convertible to `T`. Should have a begin() and end() method. + * @param range Values of the generator. + */ + template , class = std::enable_if_t::has_begin> > + Dynamic_multi_parameter_filtration(const ValueRange &range) : generators_(1, Generator(range.begin(), range.end())) + { + number_of_parameters_ = generators_[0].size(); + } + + /** + * @brief Builds filtration value with one generator that is initialized with the given range. The range is + * determined from the two given iterators. The number of parameters are therefore deduced from the distance + * between the two. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + */ + template + Dynamic_multi_parameter_filtration(Iterator it_begin, Iterator it_end) : generators_(1, Generator(it_begin, it_end)) + { + number_of_parameters_ = generators_[0].size(); + } + + /** + * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$ + * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator, + * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple + * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by two iterators. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyForwardIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + * @param number_of_parameters Negative values are associated to 0. + */ + template > > + Dynamic_multi_parameter_filtration(Iterator it_begin, Iterator it_end, int number_of_parameters) + : number_of_parameters_(number_of_parameters < 0 ? 0 : number_of_parameters), generators_() + { + // Will discard any value at the end which does not fit into a complete generator. + const size_type num_gen = std::distance(it_begin, it_end) / number_of_parameters; + + if constexpr (Ensure1Criticality) { + if (num_gen != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + + Iterator it = it_begin; + for (size_type i = 0; i < num_gen; ++i) { + Iterator endIt = it; + std::advance(endIt, number_of_parameters); + generators_.emplace_back(it, endIt); + it = endIt; + } + } + + /** + * @brief Builds filtration value with given number of parameters and values copied from the given + * @ref Dynamic_multi_parameter_filtration::Underlying_container container. + * + * @param generators Values. + * @param number_of_parameters Negative values are associated to 0. + */ + Dynamic_multi_parameter_filtration(const Underlying_container &generators, int number_of_parameters) + : number_of_parameters_(number_of_parameters < 0 ? 0 : number_of_parameters), generators_(generators) + { + GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(), + std::invalid_argument("Number of parameters cannot be 0 if the container is not empty.")); + + if constexpr (Ensure1Criticality) { + if (generators_.size() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Builds filtration value with given number of parameters and values moved from the given + * @ref Dynamic_multi_parameter_filtration::Underlying_container container. + * + * @param generators Values to move. + * @param number_of_parameters Negative values are associated to 0. + */ + Dynamic_multi_parameter_filtration(Underlying_container &&generators, int number_of_parameters) + : number_of_parameters_(number_of_parameters < 0 ? 0 : number_of_parameters), generators_(std::move(generators)) + { + GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(), + std::invalid_argument("Number of parameters cannot be 0 if the container is not empty.")); + + if constexpr (Ensure1Criticality) { + if (generators_.size() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Copy constructor. + */ + Dynamic_multi_parameter_filtration(const Dynamic_multi_parameter_filtration &other) = default; + + /** + * @brief Copy constructor. + * + * @tparam U Type convertible into `T`. + */ + template + Dynamic_multi_parameter_filtration( + const Dynamic_multi_parameter_filtration &other) + : number_of_parameters_(other.num_parameters()), generators_(other.begin(), other.end()) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (generators_.size() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Move constructor. + */ + Dynamic_multi_parameter_filtration(Dynamic_multi_parameter_filtration &&other) noexcept = default; + + ~Dynamic_multi_parameter_filtration() = default; + + /** + * @brief Assign operator. + */ + Dynamic_multi_parameter_filtration &operator=(const Dynamic_multi_parameter_filtration &other) = default; + + /** + * @brief Move assign operator. + */ + Dynamic_multi_parameter_filtration &operator=(Dynamic_multi_parameter_filtration &&other) noexcept = default; + + /** + * @brief Assign operator. + * + * @tparam U Type convertible into `T`. + */ + template + Dynamic_multi_parameter_filtration &operator=( + const Dynamic_multi_parameter_filtration &other) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (other.num_generators() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + generators_ = Underlying_container(other.begin(), other.end()); + number_of_parameters_ = other.num_parameters(); + return *this; + } + + /** + * @brief Swap operator. + */ + friend void swap(Dynamic_multi_parameter_filtration &f1, Dynamic_multi_parameter_filtration &f2) noexcept + { + f1.generators_.swap(f2.generators_); + std::swap(f1.number_of_parameters_, f2.number_of_parameters_); + } + + // VECTOR-LIKE + + using value_type = T; /**< Value type. */ + using size_type = typename Underlying_container::size_type; /**< Size type. */ + using difference_type = typename Underlying_container::difference_type; /**< Difference type. */ + using reference = value_type &; /**< Reference type. */ + using const_reference = const value_type &; /**< Const reference type. */ + using pointer = typename Underlying_container::pointer; /**< Pointer type. */ + using const_pointer = typename Underlying_container::const_pointer; /**< Const pointer type. */ + using iterator = typename Underlying_container::iterator; /**< Iterator type. */ + using const_iterator = typename Underlying_container::const_iterator; /**< Const iterator type. */ + using reverse_iterator = typename Underlying_container::reverse_iterator; /**< Reverse iterator type. */ + using const_reverse_iterator = typename Underlying_container::const_reverse_iterator; /**< Const reverse iterator. */ + + /** + * @brief Returns reference to value of parameter `p` of generator `g`. + * If the value is at +/- inf or NaN and it needs to be modified, @ref force_generator_size_to_number_of_parameters + * needs potentially to be called first such that this methods returns the right reference. + */ + reference operator()(size_type g, size_type p) + { + GUDHI_CHECK(g < generators_.size() && p < number_of_parameters_, std::out_of_range("Out of bound index.")); + if (generators_[g].size() < number_of_parameters_) return generators_[g][0]; + return generators_[g][p]; + } + + /** + * @brief Returns const reference to value of parameter `p` of generator `g`. + */ + const_reference operator()(size_type g, size_type p) const + { + GUDHI_CHECK(g < generators_.size() && p < number_of_parameters_, std::out_of_range("Out of bound index.")); + if (generators_[g].size() < number_of_parameters_) return generators_[g][0]; + return generators_[g][p]; + } + + /** + * @brief Returns const reference to the requested generator. + * + * @param g Index of the generator. + */ + const Generator &operator[](size_type g) const + { + GUDHI_CHECK(g < generators_.size(), std::out_of_range("Out of bound index.")); + return generators_[g]; + } + + /** + * @brief Returns reference to the requested generator. + * + * @param g Index of the generator. + */ + Generator &operator[](size_type g) + { + GUDHI_CHECK(g < generators_.size(), std::out_of_range("Out of bound index.")); + return generators_[g]; + } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * If the value is at +/- inf or NaN and it needs to be modified, @ref force_generator_size_to_number_of_parameters + * needs potentially to be called first such that this methods returns the right reference. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + reference operator[](const IndexRange &indices) + { + GUDHI_CHECK(indices.size() >= 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return this->operator()(g, *(++it)); + } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + const_reference operator[](const IndexRange &indices) const + { + GUDHI_CHECK(indices.size() >= 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return this->operator()(g, *(++it)); + } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. Each element stored is a whole + * generator with a size() and a operator[]. + * + * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour + * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to + * ensure this property. + */ + iterator begin() noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. Each element stored is a whole + * generator with a size() and a operator[]. + */ + const_iterator begin() const noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. Each element stored is a whole + * generator with a size() and a operator[]. + */ + const_iterator cbegin() const noexcept { return generators_.cbegin(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + iterator end() noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator end() const noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator cend() const noexcept { return generators_.cend(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * Each element stored is a whole generator with a size() and a operator[]. + * + * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour + * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to + * ensure this property. + */ + reverse_iterator rbegin() noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * Each element stored is a whole generator with a size() and a operator[]. + */ + const_reverse_iterator rbegin() const noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * Each element stored is a whole generator with a size() and a operator[]. + */ + const_reverse_iterator crbegin() const noexcept { return generators_.crbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + reverse_iterator rend() noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator rend() const noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator crend() const noexcept { return generators_.crend(); } + + /** + * @brief Returns the size of the underlying container. Corresponds exactly to @ref num_generators(), but enables to + * use the class as a classic range with a `begin`, `end` and `size` method. + */ + size_type size() const noexcept { return generators_.size(); } + + /** + * @brief Reserves space for the given number of generators in the underlying container. Does nothing if + * `Ensure1Criticality` is true. + */ + void reserve([[maybe_unused]] size_type number_of_generators) + { + if constexpr (Ensure1Criticality) { + return; + } else { + generators_.reserve(number_of_generators); + } + } + + // CONVERTERS + + // like numpy + /** + * @brief Returns a copy with entries casted into the type given as template parameter. + * + * @tparam U New type for the entries. + * @tparam OCo New value for `Co`. Default value: `Co`. + * @tparam OEns New value for `Ensure1Criticality`. Note that if `OEns` is set to true and the value is not + * 1-critical, the method will throw. Default value: `Ensure1Criticality`. + * @return Copy with new entry type. + */ + template + Dynamic_multi_parameter_filtration as_type() const + { + std::vector > out(num_generators()); + for (size_type g = 0; g < num_generators(); ++g) { + out[g] = generators_[g].template as_type(); + } + return Dynamic_multi_parameter_filtration(std::move(out), num_parameters()); + } + + // ACCESS + + /** + * @brief Returns the number of parameters in the filtration value. + */ + size_type num_parameters() const { return number_of_parameters_; } + + /** + * @brief Returns the number of generators in the filtration value, i.e. the criticality of the element. + */ + size_type num_generators() const + { + if constexpr (Ensure1Criticality) { + return 1; // for possible optimizations? If there is none, we can just keep the other version + } else { + return generators_.size(); + } + } + + /** + * @brief Returns the total number of values in the filtration value, that is, + * @ref num_parameters() * @ref num_generators(). + */ + size_type num_entries() const { return num_generators() * num_parameters(); } + + /** + * @brief Returns a filtration value with given number of parameters for which @ref is_plus_inf() returns `true` + * or an empty filtration value if `number_of_parameters` is 0. + */ + static Dynamic_multi_parameter_filtration inf(int number_of_parameters) + { + if (number_of_parameters == 0) return Dynamic_multi_parameter_filtration(); + Underlying_container out(1, Generator::inf()); + return Dynamic_multi_parameter_filtration(std::move(out), number_of_parameters); + } + + /** + * @brief Returns a filtration value with given number of parameters for which @ref is_minus_inf() returns `true` + * or an empty filtration value if `number_of_parameters` is 0. + */ + static Dynamic_multi_parameter_filtration minus_inf(int number_of_parameters) + { + if (number_of_parameters == 0) return Dynamic_multi_parameter_filtration(); + Underlying_container out(1, Generator::minus_inf()); + return Dynamic_multi_parameter_filtration(std::move(out), number_of_parameters); + } + + /** + * @brief Returns a filtration value with given number of parameters for which @ref is_nan() returns `true` + * or an empty filtration value if `number_of_parameters` is 0. + */ + static Dynamic_multi_parameter_filtration nan(int number_of_parameters) + { + if (number_of_parameters == 0) return Dynamic_multi_parameter_filtration(); + Underlying_container out(1, Generator::nan()); + return Dynamic_multi_parameter_filtration(std::move(out), number_of_parameters); + } + + // DESCRIPTORS + + /** + * @brief Returns value of `Ensure1Criticality`. + */ + static constexpr bool ensures_1_criticality() { return Ensure1Criticality; } + + /** + * @brief Returns value of `Co`. + */ + static constexpr bool has_negative_cones() { return Co; } + + /** + * @brief Returns `true` if and only if the filtration value is considered as plus infinity. + */ + [[nodiscard]] bool is_plus_inf() const + { + for (const Generator &g : generators_) { + if (!g.is_plus_inf()) return false; + } + return !generators_.empty(); + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as minus infinity. + */ + [[nodiscard]] bool is_minus_inf() const + { + for (const Generator &g : generators_) { + if (!g.is_minus_inf()) return false; + } + return !generators_.empty(); + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as NaN. + */ + [[nodiscard]] bool is_nan() const + { + if (generators_.empty()) return false; + for (const Generator &g : generators_) { + if (!g.is_nan()) return false; + } + return true; + } + + /** + * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as plus infinity, + * minus infinity or NaN. + */ + [[nodiscard]] bool is_finite() const + { + bool isInf = true, isMinusInf = true, isNan = true; + for (const Generator &g : generators_) { + if (!g.is_plus_inf()) isInf = false; + if (!g.is_minus_inf()) isMinusInf = false; + if (!g.is_nan()) isNan = false; + if (!isInf && !isMinusInf && !isNan) return true; + } + return false; + } + + // COMPARAISON OPERATORS + + /** + * @brief Returns `true` if and only if the first argument is lexicographically strictly less than the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator< "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_strict_less_than_lexicographically(const Dynamic_multi_parameter_filtration &a, + const Dynamic_multi_parameter_filtration &b) + { + if (&a == &b) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + // line order matters + if (a.is_nan()) return false; + if (b.is_nan()) return true; + if (a.is_plus_inf() || b.is_minus_inf()) return false; + if (a.is_minus_inf() || b.is_plus_inf()) return true; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + for (std::size_t p = 0U; p < a.num_parameters(); ++p) { + if constexpr (inverse) p = a.num_parameters() - 1 - p; + if (_is_nan(a.generators_[0][p]) && !_is_nan(b.generators_[0][p])) return false; + if (_is_nan(b.generators_[0][p])) return true; + if (a.generators_[0][p] < b.generators_[0][p]) return true; + if (b.generators_[0][p] < a.generators_[0][p]) return false; + if constexpr (inverse) p = a.num_parameters() - 1 - p; + } + return false; + } else { + for (std::size_t g = 0U; g < std::min(a.num_generators(), b.num_generators()); ++g) { + std::size_t gA = g; + std::size_t gB = g; + if constexpr (inverse) { + gA = a.num_generators() - 1 - g; + gB = b.num_generators() - 1 - g; + } + for (std::size_t p = 0U; p < a.num_parameters(); ++p) { + if constexpr (inverse) p = a.num_parameters() - 1 - p; + if (_is_nan(a.generators_[gA][p]) && !_is_nan(b.generators_[gB][p])) return false; + if (_is_nan(b.generators_[gB][p])) return true; + if (a.generators_[gA][p] < b.generators_[gB][p]) return true; + if (b.generators_[gB][p] < a.generators_[gA][p]) return false; + if constexpr (inverse) p = a.num_parameters() - 1 - p; + } + } + return a.num_generators() < b.num_generators(); + } + } + + /** + * @brief Returns `true` if and only if the first argument is lexicographically less than or equal to the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator<= "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_less_or_equal_than_lexicographically(const Dynamic_multi_parameter_filtration &a, + const Dynamic_multi_parameter_filtration &b) + { + if (&a == &b) return true; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + // line order matters + if (b.is_nan()) return true; + if (a.is_nan()) return false; + if (a.is_minus_inf() || b.is_plus_inf()) return true; + if (a.is_plus_inf() || b.is_minus_inf()) return false; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + for (std::size_t p = 0U; p < a.num_parameters(); ++p) { + if constexpr (inverse) p = a.num_parameters() - 1 - p; + if (_is_nan(a.generators_[0][p]) && !_is_nan(b.generators_[0][p])) return false; + if (_is_nan(b.generators_[0][p])) return true; + if (a.generators_[0][p] < b.generators_[0][p]) return true; + if (b.generators_[0][p] < a.generators_[0][p]) return false; + if constexpr (inverse) p = a.num_parameters() - 1 - p; + } + return true; + } else { + for (std::size_t g = 0U; g < std::min(a.num_generators(), b.num_generators()); ++g) { + std::size_t gA = g; + std::size_t gB = g; + if constexpr (inverse) { + gA = a.num_generators() - 1 - g; + gB = b.num_generators() - 1 - g; + } + for (std::size_t p = 0U; p < a.num_parameters(); ++p) { + if constexpr (inverse) p = a.num_parameters() - 1 - p; + if (_is_nan(a.generators_[gA][p]) && !_is_nan(b.generators_[gB][p])) return false; + if (_is_nan(b.generators_[gB][p])) return true; + if (a.generators_[gA][p] < b.generators_[gB][p]) return true; + if (b.generators_[gB][p] < a.generators_[gA][p]) return false; + if constexpr (inverse) p = a.num_parameters() - 1 - p; + } + } + return a.num_generators() <= b.num_generators(); + } + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are strictly contained in the + * cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator<(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + if (&a == &b) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + if (a.num_generators() == 0 || b.num_generators() == 0) return false; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + if (_first_dominates(a.generators_[0], b.generators_[0])) return false; + return _strictly_contains(a.generators_[0], b.generators_[0]); + } else { + for (std::size_t i = 0U; i < b.num_generators(); ++i) { + // for each generator in b, verify if it is strictly in the cone of at least one generator of a + bool isContained = false; + for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) { + // lexicographical order, so if a[j][0] dom b[j][0], than a[j'] can never strictly contain b[i] for all + // j' > j. + if (_first_dominates(a.generators_[j], b.generators_[i])) return false; + isContained = _strictly_contains(a.generators_[j], b.generators_[i]); + } + if (!isContained) return false; + } + return true; + } + } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are strictly contained in the + * cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator<=(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + if (a.num_generators() == 0 || b.num_generators() == 0) return false; + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + if (_first_strictly_dominates(a.generators_[0], b.generators_[0])) return false; + return _contains(a.generators_[0], b.generators_[0]); + } else { + // check if this curves is below other's curve + // ie for each guy in this, check if there is a guy in other that dominates him + for (std::size_t i = 0U; i < b.num_generators(); ++i) { + // for each generator in b, verify if it is in the cone of at least one generator of a + bool isContained = false; + for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) { + // lexicographical order, so if a[j][0] strictly dom b[j][0], than a[j'] can never contain b[i] for all + // j' > j. + if (_first_strictly_dominates(a.generators_[j], b.generators_[i])) return false; + isContained = _contains(a.generators_[j], b.generators_[i]); + } + if (!isContained) return false; + } + return true; + } + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are contained in or are (partially) + * equal to the cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator>(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + return b < a; + } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are contained in or are (partially) + * equal to the cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator>=(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + return b <= a; + } + + /** + * @brief Returns `true` if and only if for each \f$ i,j \f$, \f$ a(i,j) \f$ is equal to \f$ b(i,j) \f$. + */ + friend bool operator==(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + if (a.number_of_parameters_ != b.number_of_parameters_) return false; + // assumes lexicographical order for both + return a.generators_ == b.generators_; + } + + /** + * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. + */ + friend bool operator!=(const Dynamic_multi_parameter_filtration &a, const Dynamic_multi_parameter_filtration &b) + { + return !(a == b); + } + + // ARITHMETIC OPERATORS + + // opposite + /** + * @brief Returns a filtration value such that an entry at index \f$ i,j \f$ is equal to \f$ -f(i,j) \f$. + * + * Used conventions: + * - \f$ -NaN = NaN \f$. + * + * @param f Value to opposite. + * @return The opposite of @p f. + */ + friend Dynamic_multi_parameter_filtration operator-(const Dynamic_multi_parameter_filtration &f) + { + Underlying_container result(f.generators_); + std::for_each(result.begin(), result.end(), [](Generator &v) { v = -v; }); + return Dynamic_multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + // subtraction + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration operator-(Dynamic_multi_parameter_filtration f, const ValueRange &r) + { + f -= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) - f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ -f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param r First element of the subtraction. + * @param f Second element of the subtraction. + */ + template ::has_begin && + !std::is_same_v > > + friend Dynamic_multi_parameter_filtration operator-(const ValueRange &r, Dynamic_multi_parameter_filtration f) + { + for (Generator &g : f.generators_) { + g = r - g; + } + + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Dynamic_multi_parameter_filtration operator-(Dynamic_multi_parameter_filtration f, const T &val) + { + f -= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val - f(g,p) \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the subtraction. + * @param f Second element of the subtraction. + */ + friend Dynamic_multi_parameter_filtration operator-(const T &val, Dynamic_multi_parameter_filtration f) + { + for (Generator &g : f.generators_) { + g = val - g; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration &operator-=(Dynamic_multi_parameter_filtration &f, const ValueRange &r) + { + for (Generator &g : f.generators_) { + g -= r; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Dynamic_multi_parameter_filtration &operator-=(Dynamic_multi_parameter_filtration &f, const T &val) + { + for (Generator &g : f.generators_) { + g -= val; + } + return f; + } + + // addition + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration operator+(Dynamic_multi_parameter_filtration f, const ValueRange &r) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) + f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param r First element of the addition. + * @param f Second element of the addition. + */ + template ::has_begin && + !std::is_same_v > > + friend Dynamic_multi_parameter_filtration operator+(const ValueRange &r, Dynamic_multi_parameter_filtration f) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Dynamic_multi_parameter_filtration operator+(Dynamic_multi_parameter_filtration f, const T &val) + { + f += val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val + f(g,p) \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the addition. + * @param f Second element of the addition. + */ + friend Dynamic_multi_parameter_filtration operator+(const T &val, Dynamic_multi_parameter_filtration f) + { + f += val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration &operator+=(Dynamic_multi_parameter_filtration &f, const ValueRange &r) + { + for (Generator &g : f.generators_) { + g += r; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Dynamic_multi_parameter_filtration &operator+=(Dynamic_multi_parameter_filtration &f, const T &val) + { + for (Generator &g : f.generators_) { + g += val; + } + return f; + } + + // multiplication + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration operator*(Dynamic_multi_parameter_filtration f, const ValueRange &r) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) * f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param r First element of the multiplication. + * @param f Second element of the multiplication. + */ + template ::has_begin && + !std::is_same_v > > + friend Dynamic_multi_parameter_filtration operator*(const ValueRange &r, Dynamic_multi_parameter_filtration f) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Dynamic_multi_parameter_filtration operator*(Dynamic_multi_parameter_filtration f, const T &val) + { + f *= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val * f(g,p) \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the multiplication. + * @param f Second element of the multiplication. + */ + friend Dynamic_multi_parameter_filtration operator*(const T &val, Dynamic_multi_parameter_filtration f) + { + f *= val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration &operator*=(Dynamic_multi_parameter_filtration &f, const ValueRange &r) + { + for (Generator &g : f.generators_) { + g *= r; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Dynamic_multi_parameter_filtration &operator*=(Dynamic_multi_parameter_filtration &f, const T &val) + { + for (Generator &g : f.generators_) { + g *= val; + } + return f; + } + + // division + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration operator/(Dynamic_multi_parameter_filtration f, const ValueRange &r) + { + f /= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) / f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param r First element of the division. + * @param f Second element of the division. + */ + template ::has_begin && + !std::is_same_v > > + friend Dynamic_multi_parameter_filtration operator/(const ValueRange &r, Dynamic_multi_parameter_filtration f) + { + for (Generator &g : f.generators_) { + g = r / g; + } + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Dynamic_multi_parameter_filtration operator/(Dynamic_multi_parameter_filtration f, const T &val) + { + f /= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val / f(g,p) \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the division. + * @param f Second element of the division. + */ + friend Dynamic_multi_parameter_filtration operator/(const T &val, Dynamic_multi_parameter_filtration f) + { + for (Generator &g : f.generators_) { + g = val / g; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `ValueRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam ValueRange Either a range of into `T` convertible elements with a begin() and end() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Dynamic_multi_parameter_filtration &operator/=(Dynamic_multi_parameter_filtration &f, const ValueRange &r) + { + for (Generator &g : f.generators_) { + g /= r; + } + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Dynamic_multi_parameter_filtration &operator/=(Dynamic_multi_parameter_filtration &f, const T &val) + { + for (Generator &g : f.generators_) { + g /= val; + } + return f; + } + + // MODIFIERS + + /** + * @brief Sets the number of generators. If there were less generators before, new generators with default values are + * constructed. If there were more generators before, the exceed of generators is destroyed (any generator with index + * higher or equal to @p g to be more precise). If @p g is zero, the methods does nothing. + * + * Fails to compile if `Ensure1Criticality` is true. + * + * @warning All new generators will be set to infinity (`Co` is true) or -infinity (`Co` is false). That is, the new + * filtration value is not minimal anymore. Make sure to fill them with real generators or to remove them before + * using other methods. + * + * @warning Be sure to call @ref simplify if necessary after initializing all the generators. Most methods will have + * an undefined behaviour if the set of generators is not minimal or sorted. + * + * @param g New number of generators. + */ + void set_num_generators(size_type g) + { + static_assert(!Ensure1Criticality, "Number of generators cannot be set for a 1-critical only filtration value."); + + if (g == 0) return; + + generators_.resize(g); + } + + /** + * @brief If a generator is at +/- infinity or NaN, the underlying container can potentially (surely if just + * constructed with default values) only contain one element, representing the value, instead of number of parameters + * elements. This method forces the underlying container of the given generator to contain explicitly + * an elements for each parameter, if it was not already the case. + */ + void force_generator_size_to_number_of_parameters(size_type g) + { + if (g > num_generators()) return; + generators_[g].force_size_to_number_of_parameters(number_of_parameters_); + } + + /** + * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted. + * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that + * old generators disappear if they are overshadowed by the new one. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() method and the iterator + * type should satisfy the requirements of the standard `LegacyForwardIterator`. + * @param x New generator to add. Has to have the same number of parameters than @ref num_parameters(). + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool add_generator(const GeneratorRange &x) + { + if constexpr (std::is_same_v) { + if (x.is_nan()) return false; + bool xIsPlusInf = x.is_plus_inf(); + bool xIsMinusInf = x.is_minus_inf(); + if (!Co && xIsPlusInf) return false; + if (Co && xIsMinusInf) return false; + if (xIsPlusInf) { + if (is_plus_inf()) return false; + *this = inf(number_of_parameters_); + return true; + } + if (xIsMinusInf) { + if (is_minus_inf()) return false; + *this = minus_inf(number_of_parameters_); + return true; + } + } + return add_generator(x.begin(), x.end()); + } + + /** + * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted. + * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that + * old generators disappear if they are overshadowed by the new one. + * + * @tparam Iterator Iterator class satisfying the requirements of the standard `LegacyForwardIterator`. + * The dereferenced type has to be convertible to `T`. + * @param genStart Iterator pointing to the begining of the range. + * @param genEnd Iterator pointing to the end of the range. + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template + bool add_generator(Iterator genStart, Iterator genEnd) + { + GUDHI_CHECK(std::distance(genStart, genEnd) == static_cast(num_parameters()), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + size_type end = num_generators(); + + if (_generator_can_be_added(genStart, 0, end)) { + generators_.resize(end); + generators_.emplace_back(genStart, genEnd); + if constexpr (Ensure1Criticality) { + if (generators_.size() != 1) + throw std::logic_error("Multiparameter filtration value is not 1-critical anymore."); + } + std::sort(generators_.begin(), generators_.end(), Is_strictly_smaller_lexicographically()); + return true; + } + + return false; + } + + /** + * @brief Adds the given generator to the filtration value without any verifications or simplifications at the end + * of the set. + * + * Fails to compile if `Ensure1Criticality` is true. + * + * @warning If the resulting set of generators is not minimal or sorted after modification, some methods will have an + * undefined behaviour. Be sure to call @ref simplify() before using them. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x New generator to add. Must have the same number of parameters than @ref num_parameters(). + */ + template , + class = std::enable_if_t::has_begin> > + void add_guaranteed_generator(const GeneratorRange &x) + { + static_assert(!Ensure1Criticality, "Cannot add additional generator to a 1-critical only filtration value."); + + if constexpr (std::is_same_v) { + generators_.push_back(x); + } else { + GUDHI_CHECK(x.size() == num_parameters(), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + generators_.emplace_back(x.begin(), x.end()); + } + } + + /** + * @brief Simplifies the current set of generators such that it becomes minimal. Also orders it in increasing + * lexicographical order. Only necessary if generators were added "by hand" without verification either trough the + * constructor or with @ref add_guaranteed_generator "", etc. + */ + void simplify() + { + if constexpr (Ensure1Criticality) { + return; + } else { + size_type end = 0; + + for (std::size_t curr = 0; curr < generators_.size(); ++curr) { + if (!generators_[curr].is_finite()) { + if constexpr (Co) { + if (generators_[curr].is_plus_inf()) { + *this = inf(number_of_parameters_); + return; + } + } else { + if (generators_[curr].is_minus_inf()) { + *this = minus_inf(number_of_parameters_); + return; + } + } + if (end == 0) ++end; // if first element is +/-inf or nan, it should be kept at first + } else { + if (_generator_can_be_added(generators_[curr].begin(), 0, end)) { + swap(generators_[end], generators_[curr]); + ++end; + } + } + } + + generators_.resize(end); + std::sort(generators_.begin(), generators_.end(), Is_strictly_smaller_lexicographically()); + } + } + + /** + * @brief Removes all empty generators from the filtration value. If @p include_infinities is true, it also + * removes the generators at infinity or minus infinity (or with NaN value). + * If the set of generators is empty after removals, it is set to minus infinity if `Co` is false or to infinity + * if `Co` is true. + * + * @warning If the resulting set of generators is not minimal after the removals/sorting, some methods will have an + * undefined behaviour. Be sure to call @ref simplify() before using them. + * + * @param include_infinities If true, removes also infinity values. + */ + void remove_empty_generators(bool include_infinities = false) + { + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + const Generator &g = generators_[0]; + if (g.size() == 0 || (include_infinities && !g.is_finite())) generators_.clear(); + } else { + generators_.erase(std::remove_if(generators_.begin(), + generators_.end(), + [include_infinities](const Generator &a) { + return a.size() == 0 || (include_infinities && !a.is_finite()); + }), + generators_.end()); + std::sort(generators_.begin(), generators_.end(), Is_strictly_smaller_lexicographically()); + } + + if (generators_.empty()) { + generators_ = {Generator(1, Co ? T_inf : T_m_inf)}; + } + } + + /** + * @brief Sets each generator of the filtration value to the least common upper bound between it and the given value. + * + * More formally, it pushes the current generator to the cone \f$ \{ y \in \mathbb R^n : y \ge x \} \f$ + * originating in \f$ x \f$. The resulting value corresponds to the intersection of both + * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \ge this \} \cap \{ y \in \mathbb R^n : y \ge x \} \f$. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `GeneratorRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam GeneratorRange Either a range of into `T` convertible elements with a begin(), end() and size() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param x Range towards to push. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool push_to_least_common_upper_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + bool modified = false; + + for (Generator &g : generators_) { + modified |= g.push_to_least_common_upper_bound(x, exclude_infinite_values); + } + + if (modified && num_generators() > 1) simplify(); + + return modified; + } + + /** + * @brief Sets each generator of the filtration value to the greatest common lower bound between it and the given + * value. + * + * More formally, it pulls the current generator to the cone \f$ \{ y \in \mathbb R^n : y \le x \} \f$ + * originating in \f$ x \f$. The resulting value corresponds to the intersection of both + * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \le this \} \cap \{ y \in \mathbb R^n : y \le x \} \f$. + * + * @warning The operator accepts @ref Dynamic_multi_parameter_filtration with the same or different template + * parameters as `GeneratorRange`. But if the number of generators is higher than 1, only the first generator will be + * used for the operation. + * + * @tparam GeneratorRange Either a range of into `T` convertible elements with a begin(), end() and size() method, + * or @ref Dynamic_multi_parameter_filtration with `U` convertible into `T`. + * @param x Range towards to pull. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool pull_to_greatest_common_lower_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + bool modified = false; + + for (Generator &g : generators_) { + modified |= g.pull_to_greatest_common_lower_bound(x, exclude_infinite_values); + } + + if (modified && num_generators() > 1) simplify(); + + return modified; + } + + /** + * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. + * + * @tparam OneDimArray A range of values convertible into `T` ordered by increasing value. Has to implement + * a begin, end and operator[] method. + * @param grid Vector of @p OneDimArray with size at least number of filtration parameters. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. + */ + template + void project_onto_grid(const std::vector &grid, bool coordinate = true) + { + GUDHI_CHECK( + grid.size() >= num_parameters(), + std::invalid_argument("The grid should not be smaller than the number of parameters in the filtration value.")); + + for (Generator &g : generators_) { + g.project_onto_grid(grid, coordinate); + } + + if (!coordinate && num_generators() > 1) simplify(); + } + + // FONCTIONNALITIES + + /** + * @brief Returns a generator with the minimal values of all parameters in any generator of the given filtration + * value. That is, the greatest lower bound of all generators. + */ + friend Dynamic_multi_parameter_filtration factorize_below(const Dynamic_multi_parameter_filtration &f) + { + if (f.num_generators() <= 1) return f; + + bool isMinusInf = true; + bool nan = true; + Underlying_container result(1, Generator(f.num_parameters(), T_inf)); + for (size_type p = 0; p < f.num_parameters(); ++p) { + for (size_type g = 0; g < f.num_generators(); ++g) { + T val = f(g, p); + if (!_is_nan(val)) { + nan = false; + result[0][p] = val < result[0][p] ? val : result[0][p]; + } + } + if (nan) + result[0][p] = std::numeric_limits::quiet_NaN(); + else + nan = true; + if (result[0][p] != T_m_inf) isMinusInf = false; + } + + if (isMinusInf) result = {Generator::minus_inf()}; + + return Dynamic_multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + /** + * @brief Returns a generator with the maximal values of all parameters in any generator of the given filtration + * value. That is, the least upper bound of all generators. + */ + friend Dynamic_multi_parameter_filtration factorize_above(const Dynamic_multi_parameter_filtration &f) + { + if (f.num_generators() <= 1) return f; + + bool isPlusInf = true; + bool nan = true; + Underlying_container result(1, Generator(f.num_parameters(), T_m_inf)); + for (size_type p = 0; p < f.num_parameters(); ++p) { + for (size_type g = 0; g < f.num_generators(); ++g) { + T val = f(g, p); + if (!_is_nan(val)) { + nan = false; + result[0][p] = val > result[0][p] ? val : result[0][p]; + } + } + if (nan) + result[0][p] = std::numeric_limits::quiet_NaN(); + else + nan = true; + if (result[0][p] != T_inf) isPlusInf = false; + } + + if (isPlusInf) result = {Generator::inf()}; + + return Dynamic_multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + /** + * @brief Computes the smallest (resp. the greatest if `Co` is true) scalar product of the all generators with the + * given vector. + * + * @tparam U Arithmetic type of the result. Default value: `T`. + * @param f Filtration value. + * @param x Vector of coefficients. + * @return Scalar product of @p f with @p x. + */ + template + friend U compute_linear_projection(const Dynamic_multi_parameter_filtration &f, const std::vector &x) + { + if (f.num_generators() == 1) return compute_linear_projection(f.generators_[0], x); + + if constexpr (Co) { + U projection = std::numeric_limits::lowest(); + for (const Generator &g : f.generators_) { + // Order in the max important to spread possible NaNs + projection = std::max(compute_linear_projection(g, x), projection); + } + return projection; + } else { + U projection = std::numeric_limits::max(); + for (const Generator &g : f.generators_) { + // Order in the min important to spread possible NaNs + projection = std::min(compute_linear_projection(g, x), projection); + } + return projection; + } + } + + /** + * @brief Computes the euclidean distance from the first parameter to the second parameter as the minimum of + * all Euclidean distances between a generator of @p f and a generator of @p other. + * + * @param f Source filtration value. + * @param other Target filtration value. + * @return Euclidean distance between @p f and @p other. + */ + template + friend U compute_euclidean_distance_to(const Dynamic_multi_parameter_filtration &f, + const Dynamic_multi_parameter_filtration &other) + { + GUDHI_CHECK(f.num_parameters() == other.num_parameters(), + std::invalid_argument("We cannot compute the distance between two points of different dimensions.")); + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + return compute_euclidean_distance_to(f.generators_[0], other.generators_[0]); + } else { + U res = std::numeric_limits::max(); + for (const Generator &g1 : f.generators_) { + for (const Generator &g2 : other.generators_) { + // Order in the min important to spread possible NaNs + res = std::min(compute_euclidean_distance_to(g1, g2), res); + } + } + return res; + } + } + + /** + * @brief Computes the norm of the given filtration value. + * + * The filtration value is seen as a \f$ num_generators x num_parameters \f$ matrix and a standard Frobenius norm + * is computed from it: the square root of the sum of the squares of all elements in the matrix. + * + * @param f Filtration value. + * @return The norm of @p f. + */ + template + friend U compute_norm(const Dynamic_multi_parameter_filtration &f) + { + // Frobenius norm with matrix g x p based on Euclidean norm + U out = 0; + for (const Generator &g : f.generators_) { + out += compute_squares(g); + } + if constexpr (std::is_integral_v) { + // to avoid Windows issue that don't know how to cast integers for cmath methods + return std::sqrt(static_cast(out)); + } else { + return std::sqrt(out); + } + } + + /** + * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries + * in the given filtration value. + * The grid has to be represented as a vector of vectors of ordered values convertible into `OutValue`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. + * + * @tparam OutValue Signed arithmetic type. Default value: std::int32_t. + * @tparam U Type which is convertible into `OutValue`. + * @param f Filtration value to project. + * @param grid Vector of vectors to project into. + * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is, + * the projection of \f$ f(g,p) \f$ is \f$ grid[p][out(g,p)] \f$. + */ + template + friend Dynamic_multi_parameter_filtration compute_coordinates_in_grid( + Dynamic_multi_parameter_filtration f, + const std::vector > &grid) + { + // TODO: by replicating the code of "project_onto_grid", this could be done with just one copy + // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really + // necessary in the use cases. To see later. + f.project_onto_grid(grid); + if constexpr (std::is_same_v) { + return f; + } else { + return f.as_type(); + } + } + + /** + * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration + * value. That is, if \f$ out \f$ is the result, \f$ out(g,p) = grid[p][f(g,p)] \f$. Assumes therefore, that the + * values stored in the filtration value corresponds to indices existing in the given grid. + * + * @tparam U Signed arithmetic type. + * @param f Filtration value storing coordinates compatible with `grid`. + * @param grid Vector of vector. + * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out(g,p) = grid[p][f(g,p)] \f$. + */ + template + friend Dynamic_multi_parameter_filtration evaluate_coordinates_in_grid( + const Dynamic_multi_parameter_filtration &f, + const std::vector > &grid) + { + GUDHI_CHECK(grid.size() >= f.num_parameters(), + std::invalid_argument( + "The size of the grid should correspond to the number of parameters in the filtration value.")); + + std::vector > outVec(f.num_generators()); + + size_type i = 0; + for (const Generator &g : f.generators_) { + outVec[i] = evaluate_coordinates_in_grid(g, grid); + ++i; + } + + Dynamic_multi_parameter_filtration out(std::move(outVec), f.num_parameters()); + if constexpr (!Ensure1Criticality) + if (out.num_generators() > 1) out.simplify(); + return out; + } + + // UTILITIES + + /** + * @brief Outstream operator. + */ + friend std::ostream &operator<<(std::ostream &stream, const Dynamic_multi_parameter_filtration &f) + { + const size_type num_gen = f.num_generators(); + const size_type num_param = f.num_parameters(); + + stream << "( k = " << num_gen << " ) ( p = " << num_param << " ) [ "; + for (size_type g = 0; g < num_gen; ++g) { + stream << f.generators_[g]; + if (g < num_gen - 1) stream << "; "; + } + stream << " ]"; + + return stream; + } + + /** + * @brief Instream operator. + */ + friend std::istream &operator>>(std::istream &stream, Dynamic_multi_parameter_filtration &f) + { + size_type num_gen; + size_type num_param; + char delimiter; + stream >> delimiter; // ( + stream >> delimiter; // k + stream >> delimiter; // = + stream >> num_gen; + if (!stream.good()) + throw std::invalid_argument("Invalid incoming stream format for Dynamic_multi_parameter_filtration (num_gen)."); + f.generators_.resize(num_gen); + stream >> delimiter; // ) + stream >> delimiter; // ( + stream >> delimiter; // p + stream >> delimiter; // = + stream >> num_param; + if (!stream.good()) + throw std::invalid_argument("Invalid incoming stream format for Dynamic_multi_parameter_filtration (num_param)."); + f.number_of_parameters_ = num_param; + stream >> delimiter; // ) + stream >> delimiter; // [ + if (delimiter != '[') + throw std::invalid_argument("Invalid incoming stream format for Dynamic_multi_parameter_filtration ([)."); + if (num_gen == 0) return stream; + for (size_type i = 0; i < num_gen; ++i) { + stream >> f.generators_[i]; + stream >> delimiter; // ; or last ] + } + if (delimiter != ']') + throw std::invalid_argument("Invalid incoming stream format for Dynamic_multi_parameter_filtration (])."); + + return stream; + } + + /** + * @brief Returns true if and only if the given filtration value is at plus infinity. + */ + friend bool is_positive_infinity(const Dynamic_multi_parameter_filtration &f) + { + return f.is_plus_inf(); + } + + /** + * @brief Adds the generators of the second argument to the first argument. If `Ensure1Criticality` is true, + * the method assumes that the two filtration values are comparable, that is, that the result of the union is also + * 1-critical. A check for this is only done in Debug Mode, as it is costly. + * + * @param f1 Filtration value to modify. + * @param f2 Filtration value to merge with the first one. Should have the same number of parameters than the other. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool unify_lifetimes(Dynamic_multi_parameter_filtration &f1, const Dynamic_multi_parameter_filtration &f2) + { + GUDHI_CHECK(f1.num_parameters() == f2.num_parameters() || !f1.is_finite() || !f2.is_finite(), + "Cannot unify two filtration values with different number of parameters."); + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + // if general case is kept: add (num_gen == 1) test to throw if unification is not 1-critical anymore. + if constexpr (Ensure1Criticality) { + // WARNING: costly check + GUDHI_CHECK(f1 <= f2 || f2 <= f1, + "When 1-critical only, two non-comparable filtration values cannot be unified."); + + if constexpr (Co) { + return f1.push_to_least_common_upper_bound(f2); + } else { + return f1.pull_to_greatest_common_lower_bound(f2); + } + } else { + bool modified = false; + for (const Generator &g : f2.generators_) { + modified |= f1.add_generator(g); + } + return modified; + } + } + + /** + * @brief Stores in the first argument the origins of the cones in the intersection of the positive + * (negative if `Co` is true) cones generated by the two arguments. + * + * @param f1 First set of cones which will be modified. + * @param f2 Second set of cones. Should have the same number of parameters than the first one. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool intersect_lifetimes(Dynamic_multi_parameter_filtration &f1, const Dynamic_multi_parameter_filtration &f2) + { + if (f1.is_nan() || f2.is_nan()) return false; + + if constexpr (Co) { + if (f1.is_plus_inf()) { + if (f2.is_plus_inf()) return false; + f1 = f2; + return true; + } + if (f1.is_minus_inf()) { + return false; + } + } else { + if (f1.is_minus_inf()) { + if (f2.is_minus_inf()) return false; + f1 = f2; + return true; + } + if (f1.is_plus_inf()) { + return false; + } + } + + GUDHI_CHECK(f1.num_parameters() == f2.num_parameters(), + "Cannot intersect two filtration values with different number of parameters."); + + if constexpr (Ensure1Criticality) { + if constexpr (Co) { + return f1.pull_to_greatest_common_lower_bound(f2); + } else { + return f1.push_to_least_common_upper_bound(f2); + } + } else { + const size_type num_param = f1.num_parameters(); + Dynamic_multi_parameter_filtration res = Co ? minus_inf(num_param) : inf(num_param); + std::vector newGen(num_param); + // TODO: see if the order can be used to avoid g1 * g2 add_generator and + // perhaps even to replace add_generator by add_guaranteed_generator + for (size_type g1 = 0; g1 < f1.num_generators(); ++g1) { + for (size_type g2 = 0; g2 < f2.num_generators(); ++g2) { + GUDHI_CHECK(f1.generators_[g1].size() == num_param, "Filtration value f1 is not minimal."); + GUDHI_CHECK(f2.generators_[g2].size() == num_param, "Filtration value f2 is not minimal."); + for (size_type p = 0; p < num_param; ++p) { + if constexpr (Co) { + newGen[p] = std::min(f1(g1, p), f2(g2, p)); + } else { + newGen[p] = std::max(f1(g1, p), f2(g2, p)); + } + } + res.add_generator(newGen); + } + } + swap(f1, res); + + return f1 != res; + } + } + + /** + * @brief Serialize given value into the buffer at given pointer. + * + * @param value Value to serialize. + * @param start Pointer to the start of the space in the buffer where to store the serialization. + * @return End position of the serialization in the buffer. + */ + friend char *serialize_value_to_char_buffer(const Dynamic_multi_parameter_filtration &value, char *start) + { + const std::size_t nberOfGenerators = value.generators_.size(); + const std::size_t type_size = sizeof(std::size_t); + memcpy(start, &value.number_of_parameters_, type_size); + memcpy(start + type_size, &nberOfGenerators, type_size); + char *curr = start + type_size + type_size; + for (const Generator &g : value) { + curr = serialize_value_to_char_buffer(g, curr); + } + return curr; + } + + /** + * @brief Deserialize the value from a buffer at given pointer and stores it in given value. + * + * @param value Value to fill with the deserialized filtration value. + * @param start Pointer to the start of the space in the buffer where the serialization is stored. + * @return End position of the serialization in the buffer. + */ + friend const char *deserialize_value_from_char_buffer(Dynamic_multi_parameter_filtration &value, const char *start) + { + const std::size_t type_size = sizeof(std::size_t); + std::size_t nberOfGenerators; + memcpy(&value.number_of_parameters_, start, type_size); + memcpy(&nberOfGenerators, start + type_size, type_size); + value.generators_.resize(nberOfGenerators); + const char *curr = start + type_size + type_size; + for (Generator &g : value) { + curr = deserialize_value_from_char_buffer(g, curr); + } + return curr; + } + + /** + * @brief Returns the serialization size of the given filtration value. + */ + friend std::size_t get_serialization_size_of(const Dynamic_multi_parameter_filtration &value) + { + std::size_t genSizes = sizeof(std::size_t) * 2; + for (const Generator &g : value) { + genSizes += get_serialization_size_of(g); + } + return genSizes; + } + + /** + * @brief Plus infinity value of an entry of the filtration value. + */ + constexpr static const T T_inf = Generator::T_inf; + + /** + * @brief Minus infinity value of an entry of the filtration value. + */ + constexpr static const T T_m_inf = Generator::T_m_inf; + + private: + size_type number_of_parameters_; /**< Number of parameters. */ + Underlying_container generators_; /**< Container of the filtration value elements. */ + + constexpr static bool _is_nan(T val) + { + if constexpr (std::is_integral_v) { + // to avoid Windows issue which don't know how to cast integers for cmath methods + return false; + } else { + return std::isnan(val); + } + } + + /** + * @brief Verifies if @p b is strictly contained in the positive cone originating in `a`. + */ + static bool _strictly_contains(const Generator &a, const Generator &b) + { + if constexpr (Co) + return a > b; + else { + return a < b; + } + } + + /** + * @brief Verifies if @p b is contained in the positive cone originating in `a`. + */ + static bool _contains(const Generator &a, const Generator &b) + { + if constexpr (Co) + return a >= b; + else { + return a <= b; + } + } + + /** + * @brief Verifies if the first element of @p b strictly dominates the first element of `a`. + */ + static bool _first_strictly_dominates(const Generator &a, const Generator &b) + { + if constexpr (Co) { + return a.size() != 0 && b.size() != 0 && a[0] < b[0]; + } else { + return a.size() != 0 && b.size() != 0 && a[0] > b[0]; + } + } + + /** + * @brief Verifies if the first element of @p b dominates the first element of `a`. + */ + static bool _first_dominates(const Generator &a, const Generator &b) + { + if constexpr (Co) { + return a.size() != 0 && b.size() != 0 && a[0] <= b[0]; + } else { + return a.size() != 0 && b.size() != 0 && a[0] >= b[0]; + } + } + + enum class Rel : std::uint8_t { EQUAL, DOMINATES, IS_DOMINATED, NONE }; + + template + static Rel _get_domination_relation(const Generator &a, Iterator itB) + { + if (a.is_nan()) return Rel::NONE; + + bool equal = true; + bool allGreater = true; + bool allSmaller = true; + bool allNaNA = true; + bool allNaNB = true; + for (unsigned int i = 0; i < a.size(); ++i) { + if (a[i] < *itB) { + if (!allSmaller) return Rel::NONE; + equal = false; + allGreater = false; + } else if (a[i] > *itB) { + if (!allGreater) return Rel::NONE; + equal = false; + allSmaller = false; + } + if (!_is_nan(a[i])) allNaNA = false; + if (!_is_nan(*itB)) allNaNB = false; + ++itB; + } + if (allNaNA || allNaNB) return Rel::IS_DOMINATED; + if (equal) return Rel::EQUAL; + + if constexpr (Co) { + if (allSmaller) return Rel::DOMINATES; + return Rel::IS_DOMINATED; + } else { + if (allGreater) return Rel::DOMINATES; + return Rel::IS_DOMINATED; + } + } + + /** + * @brief Verifies how x can be added as a new generator with respect to an already existing generator, represented + * by `generators_[curr]`. If x is dominated by or is equal to `generators_[curr]`, it cannot be added. If it + * dominates `generators_[curr]`, it has to replace `generators_[curr]`. If there is no relation between both, + * `generators_[curr]` has no influence on the addition of x. + * + * Assumes between 'curr' and 'end' everything is simplified: + * no nan values and if there is an inf/-inf, then 'end - curr == 1'. + */ + template + bool _generator_can_be_added(Iterator x, size_type curr, size_type &end) + { + // assumes that everything between curr and end is simplified + // so, only generators_[curr] can be at inf or -inf. + if constexpr (Co) { + if (generators_[curr].is_plus_inf()) { + return false; + } + if (generators_[curr].is_minus_inf()) { + end = curr; + return true; + } + } else { + if (generators_[curr].is_minus_inf()) { + return false; + } + if (generators_[curr].is_plus_inf()) { + end = curr; + return true; + } + } + + while (curr != end) { + Rel res = _get_domination_relation(generators_[curr], x); + if (res == Rel::IS_DOMINATED || res == Rel::EQUAL) return false; // x dominates or is equal + if (res == Rel::DOMINATES) { // x is dominated + --end; + swap(generators_[curr], generators_[end]); + } else { // no relation + ++curr; + } + } + return true; + } + + struct Is_strictly_smaller_lexicographically { + // assumes both generators have the same length if not infinite/nan. + bool operator()(const Generator &g1, const Generator &g2) + { + // orders such that -inf < 'finite values' < inf < NaN. + + if (g1.is_nan() || g2.is_nan()) return !g1.is_nan(); + if (g1.is_plus_inf()) return false; + if (g2.is_plus_inf()) return true; + if (g2.is_minus_inf()) return false; + if (g1.is_minus_inf()) return true; + + // g1 and g2 have to be finite and of the same size + for (std::size_t i = 0; i < g1.size(); ++i) { + if (g1[i] != g2[i]) return g1[i] < g2[i]; + } + return false; + } + }; +}; + +} // namespace Gudhi::multi_filtration + +namespace std { + +template +class numeric_limits > +{ + public: + using Filtration_value = Gudhi::multi_filtration::Dynamic_multi_parameter_filtration; + + static constexpr bool has_infinity = true; + static constexpr bool has_quiet_NaN = true; + + static constexpr Filtration_value infinity(std::size_t p = 1) noexcept { return Filtration_value::inf(p); }; + + // non-standard + static constexpr Filtration_value minus_infinity(std::size_t p = 1) noexcept + { + return Filtration_value::minus_inf(p); + }; + + static constexpr Filtration_value max() noexcept(false) + { + throw std::logic_error( + "The max value cannot be represented with no finite numbers of parameters." + "Use `max(number_of_parameters)` instead"); + }; + + static constexpr Filtration_value max(std::size_t p) noexcept + { + return Filtration_value(p, std::numeric_limits::max()); + }; + + static constexpr Filtration_value lowest(std::size_t p = 1) noexcept { return Filtration_value::minus_inf(p); }; + + static constexpr Filtration_value quiet_NaN(std::size_t p = 1) noexcept { return Filtration_value::nan(p); }; +}; + +} // namespace std + +#endif // MF_DYNAMIC_MULTI_PARAMETER_FILTRATION_H_ diff --git a/multipers/gudhi/gudhi/Fields/Multi_field.h b/multipers/gudhi/gudhi/Fields/Multi_field.h index 364673d8..b967dc31 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field.h @@ -36,66 +36,83 @@ namespace persistence_fields { * @tparam maximum Interval closed upper bound. */ template -class Multi_field_element { +class Multi_field_element +{ public: - using Element = mpz_class; /**< Type for the elements in the field. */ + using Element = mpz_class; /**< Type for the elements in the field. */ using Characteristic = Element; /**< Type for the field characteristic. */ /** * @brief Default constructor. Sets the element to 0. */ - Multi_field_element(); + Multi_field_element() : element_(0) + { + static_assert(maximum >= 2, "Characteristics have to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + + if (productOfAllCharacteristics_ == 1) + throw std::runtime_error("The given interval does not contain a prime number."); + } + /** * @brief Constructor setting the element to the given value. * * @param element Value of the element. */ - Multi_field_element(const Element& element); - /** - * @brief Copy constructor. - * - * @param toCopy Element to copy. - */ - Multi_field_element(const Multi_field_element& toCopy); - /** - * @brief Move constructor. - * - * @param toMove Element to move. - */ - Multi_field_element(Multi_field_element&& toMove) noexcept; + Multi_field_element(Element element) : element_(std::move(element)) + { + static_assert(maximum >= 2, "Characteristics has to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + + if (productOfAllCharacteristics_ == 1) + throw std::runtime_error("The given interval does not contain a prime number."); + + mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } /** * @brief operator+= */ - friend void operator+=(Multi_field_element& f1, Multi_field_element const& f2) { + friend void operator+=(Multi_field_element& f1, Multi_field_element const& f2) + { f1.element_ += f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator+ */ - friend Multi_field_element operator+(Multi_field_element f1, Multi_field_element const& f2) { + friend Multi_field_element operator+(Multi_field_element f1, Multi_field_element const& f2) + { f1 += f2; return f1; } + /** * @brief operator+= */ - friend void operator+=(Multi_field_element& f, const Element& v) { + friend void operator+=(Multi_field_element& f, const Element& v) + { f.element_ += v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator+ */ - friend Multi_field_element operator+(Multi_field_element f, const Element& v) { + friend Multi_field_element operator+(Multi_field_element f, const Element& v) + { f += v; return f; } + /** * @brief operator+ */ - friend Element operator+(Element v, Multi_field_element const& f) { + friend Element operator+(Element v, Multi_field_element const& f) + { v += f.element_; mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return v; @@ -104,35 +121,44 @@ class Multi_field_element { /** * @brief operator-= */ - friend void operator-=(Multi_field_element& f1, Multi_field_element const& f2) { + friend void operator-=(Multi_field_element& f1, Multi_field_element const& f2) + { f1.element_ -= f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator- */ - friend Multi_field_element operator-(Multi_field_element f1, Multi_field_element const& f2) { + friend Multi_field_element operator-(Multi_field_element f1, Multi_field_element const& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= */ - friend void operator-=(Multi_field_element& f, const Element& v) { + friend void operator-=(Multi_field_element& f, const Element& v) + { f.element_ -= v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator- */ - friend Multi_field_element operator-(Multi_field_element f, const Element& v) { + friend Multi_field_element operator-(Multi_field_element f, const Element& v) + { f -= v; return f; } + /** * @brief operator- */ - friend Element operator-(Element v, Multi_field_element const& f) { + friend Element operator-(Element v, Multi_field_element const& f) + { // Element e(v); if (v >= productOfAllCharacteristics_) mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); @@ -144,35 +170,44 @@ class Multi_field_element { /** * @brief operator*= */ - friend void operator*=(Multi_field_element& f1, Multi_field_element const& f2) { + friend void operator*=(Multi_field_element& f1, Multi_field_element const& f2) + { f1.element_ *= f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator* */ - friend Multi_field_element operator*(Multi_field_element f1, Multi_field_element const& f2) { + friend Multi_field_element operator*(Multi_field_element f1, Multi_field_element const& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= */ - friend void operator*=(Multi_field_element& f, const Element& v) { + friend void operator*=(Multi_field_element& f, const Element& v) + { f.element_ *= v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator* */ - friend Multi_field_element operator*(Multi_field_element f, const Element& v) { + friend Multi_field_element operator*(Multi_field_element f, const Element& v) + { f *= v; return f; } + /** * @brief operator* */ - friend Element operator*(Element v, Multi_field_element const& f) { + friend Element operator*(Element v, Multi_field_element const& f) + { v *= f.element_; mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return v; @@ -181,35 +216,43 @@ class Multi_field_element { /** * @brief operator== */ - friend bool operator==(const Multi_field_element& f1, const Multi_field_element& f2) { + friend bool operator==(const Multi_field_element& f1, const Multi_field_element& f2) + { return f1.element_ == f2.element_; } + /** * @brief operator== */ - friend bool operator==(const Element& v, const Multi_field_element& f) { + friend bool operator==(const Element& v, const Multi_field_element& f) + { if (v < productOfAllCharacteristics_) return v == f.element_; Element e(v); mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return e == f.element_; } + /** * @brief operator== */ - friend bool operator==(const Multi_field_element& f, const Element& v) { + friend bool operator==(const Multi_field_element& f, const Element& v) + { if (v < productOfAllCharacteristics_) return v == f.element_; Element e(v); mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return e == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Multi_field_element& f1, const Multi_field_element& f2) { return !(f1 == f2); } + /** * @brief operator!= */ friend bool operator!=(const Element& v, const Multi_field_element& f) { return !(v == f); } + /** * @brief operator!= */ @@ -218,31 +261,37 @@ class Multi_field_element { /** * @brief Assign operator. */ - Multi_field_element& operator=(Multi_field_element other); - /** - * @brief Assign operator. - */ - Multi_field_element& operator=(const Element& value); + Multi_field_element& operator=(const Element& value) + { + mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return *this; + } + /** * @brief Swap operator. */ - friend void swap(Multi_field_element& f1, Multi_field_element& f2) { std::swap(f1.element_, f2.element_); } + friend void swap(Multi_field_element& f1, Multi_field_element& f2) noexcept { std::swap(f1.element_, f2.element_); } /** * @brief Casts the element into an unsigned int. */ - operator unsigned int() const; + operator unsigned int() const { return element_.get_ui(); } + /** * @brief Casts the element into an mpz_class. */ - operator mpz_class() const; + explicit operator mpz_class() const { return element_; } /** * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. * * @return The inverse. */ - Multi_field_element get_inverse() const; + [[nodiscard]] Multi_field_element get_inverse() const + { + return get_partial_inverse(productOfAllCharacteristics_).first; + } + /** * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, * see @cite boissonnat:hal-00922572. @@ -250,21 +299,42 @@ class Multi_field_element { * @param productOfCharacteristics Sub-product of the characteristics. * @return Pair of the inverse and the characteristic the inverse corresponds to. */ - std::pair get_partial_inverse( - const Characteristic& productOfCharacteristics) const; + [[nodiscard]] std::pair get_partial_inverse( + const Characteristic& productOfCharacteristics) const + { + Characteristic QR; + mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) + + if (QR == productOfCharacteristics) return {Multi_field_element(), multiplicativeID_}; // partial inverse is 0 + + Characteristic QT = productOfCharacteristics / QR; + + Characteristic inv_qt; + mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; + } /** * @brief Returns the additive identity of a field. * * @return The additive identity of a field. */ - static Multi_field_element get_additive_identity(); + static Multi_field_element get_additive_identity() { return Multi_field_element(); } + /** * @brief Returns the multiplicative identity of a field. * * @return The multiplicative identity of a field. */ - static Multi_field_element get_multiplicative_identity(); + static Multi_field_element get_multiplicative_identity() + { + return Multi_field_element(multiplicativeID_); + } + /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. @@ -272,22 +342,33 @@ class Multi_field_element { * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return The partial multiplicative identity of the multi-field. */ - static Multi_field_element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics); + static Multi_field_element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) + { + if (productOfCharacteristics == 0) { + return Multi_field_element(multiplicativeID_); + } + Multi_field_element mult; + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; + } + /** * @brief Returns the product of all characteristics. * * @return The product of all characteristics. */ - static Characteristic get_characteristic(); + static Characteristic get_characteristic() { return productOfAllCharacteristics_; } /** * @brief Returns the value of the element. * * @return Value of the element. */ - Element get_value() const; - - // static constexpr bool handles_only_z2() { return false; } + [[nodiscard]] Element get_value() const { return element_; } private: Element element_; @@ -327,9 +408,8 @@ class Multi_field_element { if (productOfAllCharacteristics_ == 1) return res; - for (unsigned int i = 0; i < primes_.size(); ++i) { - unsigned int p = primes_[i]; - res.push_back(productOfAllCharacteristics_ / p); + for (unsigned int p : primes_) { + res.emplace_back(productOfAllCharacteristics_ / p); mpz_powm_ui(res.back().get_mpz_t(), res.back().get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); } @@ -346,137 +426,18 @@ class Multi_field_element { return res; }();*/ - static constexpr bool _is_prime(const int p); -}; - -template -inline Multi_field_element::Multi_field_element() : element_(0) { - static_assert(maximum >= 2, "Characteristics have to be positive."); - static_assert(minimum <= maximum, "The given interval is not valid."); - static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); - - if (productOfAllCharacteristics_ == 1) - throw std::runtime_error("The given interval does not contain a prime number."); -} - -template -inline Multi_field_element::Multi_field_element(const Element& element) : element_(element) { - static_assert(maximum >= 2, "Characteristics has to be positive."); - static_assert(minimum <= maximum, "The given interval is not valid."); - static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); - - if (productOfAllCharacteristics_ == 1) - throw std::runtime_error("The given interval does not contain a prime number."); - - mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); -} - -template -inline Multi_field_element::Multi_field_element(const Multi_field_element& toCopy) - : element_(toCopy.element_) {} - -template -inline Multi_field_element::Multi_field_element( - Multi_field_element&& toMove) noexcept - : element_(std::move(toMove.element_)) {} - -template -inline Multi_field_element& Multi_field_element::operator=( - Multi_field_element other) { - std::swap(element_, other.element_); - return *this; -} - -template -inline Multi_field_element& Multi_field_element::operator=( - const Element& value) { - mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); - return *this; -} - -template -inline Multi_field_element::operator unsigned int() const { - return element_.get_ui(); -} - -template -inline Multi_field_element::operator mpz_class() const { - return element_; -} - -template -inline Multi_field_element Multi_field_element::get_inverse() const { - return get_partial_inverse(productOfAllCharacteristics_).first; -} - -template -inline std::pair, - typename Multi_field_element::Characteristic> -Multi_field_element::get_partial_inverse(const Characteristic& productOfCharacteristics) const { - Characteristic QR; - mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) - - if (QR == productOfCharacteristics) return {Multi_field_element(), multiplicativeID_}; // partial inverse is 0 - - Characteristic QT = productOfCharacteristics / QR; - - Characteristic inv_qt; - mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); + static constexpr bool _is_prime(const int p) + { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; - auto res = get_partial_multiplicative_identity(QT); - res *= inv_qt; + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; - return {res, QT}; -} - -template -inline Multi_field_element Multi_field_element::get_additive_identity() { - return Multi_field_element(); -} - -template -inline Multi_field_element Multi_field_element::get_multiplicative_identity() { - return Multi_field_element(multiplicativeID_); -} - -template -inline Multi_field_element Multi_field_element::get_partial_multiplicative_identity( - const Characteristic& productOfCharacteristics) { - if (productOfCharacteristics == 0) { - return Multi_field_element(multiplicativeID_); - } - Multi_field_element mult; - for (unsigned int idx = 0; idx < primes_.size(); ++idx) { - if ((productOfCharacteristics % primes_[idx]) == 0) { - mult += partials_[idx]; - } + return true; } - return mult; -} - -template -inline typename Multi_field_element::Characteristic -Multi_field_element::get_characteristic() { - return productOfAllCharacteristics_; -} - -template -inline typename Multi_field_element::Element Multi_field_element::get_value() - const { - return element_; -} - -template -inline constexpr bool Multi_field_element::_is_prime(const int p) { - if (p <= 1) return false; - if (p <= 3) return true; - if (p % 2 == 0 || p % 3 == 0) return false; - - for (long i = 5; i * i <= p; i = i + 6) - if (p % i == 0 || p % (i + 2) == 0) return false; - - return true; -} +}; } // namespace persistence_fields } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Fields/Multi_field_operators.h b/multipers/gudhi/gudhi/Fields/Multi_field_operators.h index 20222e32..81dd0897 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field_operators.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field_operators.h @@ -34,56 +34,37 @@ namespace persistence_fields { class Multi_field_operators { public: - using Element = mpz_class; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Element = mpz_class; /**< Type for the elements in the field. */ + using Characteristic = Element; /**< Type for the field characteristic. */ + + inline static const Characteristic nullCharacteristic = 0; /**< Value of a non initialized characteristic. */ /** * @brief Default constructor, sets the product of all characteristics to 0. */ - Multi_field_operators() : productOfAllCharacteristics_(0) /* , multiplicativeID_(1) */ - {} + Multi_field_operators() : productOfAllCharacteristics_(nullCharacteristic) /* , multiplicativeID_(1) */ {} + /** * @brief Constructor setting the characteristics to all prime numbers between the two given integers. - * + * * @param minCharacteristic Smallest value of a prime. * @param maxCharacteristic Highest value of a prime. */ Multi_field_operators(int minCharacteristic, int maxCharacteristic) - : productOfAllCharacteristics_(0) //, multiplicativeID_(1) + : productOfAllCharacteristics_(nullCharacteristic) //, multiplicativeID_(1) { set_characteristic(minCharacteristic, maxCharacteristic); } - /** - * @brief Copy constructor. - * - * @param toCopy Operators to copy. - */ - Multi_field_operators(const Multi_field_operators& toCopy) - : primes_(toCopy.primes_), - productOfAllCharacteristics_(toCopy.productOfAllCharacteristics_), - partials_(toCopy.partials_) /* , - multiplicativeID_(toCopy.multiplicativeID_) */ - {} - /** - * @brief Move constructor. - * - * @param toMove Operators to move. - */ - Multi_field_operators(Multi_field_operators&& toMove) noexcept - : primes_(std::move(toMove.primes_)), - productOfAllCharacteristics_(std::move(toMove.productOfAllCharacteristics_)), - partials_(std::move(toMove.partials_)) /* , - multiplicativeID_(std::move(toMove.multiplicativeID_)) */ - {} /** * @brief Set the characteristics of the field, which are stored in a single value as a product of all of them. * The characteristics will be all prime numbers in the given interval. - * + * * @param minimum Smallest value of a prime. * @param maximum Highest value of a prime. */ - void set_characteristic(int minimum, int maximum) { + void set_characteristic(int minimum, int maximum) + { if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); if (minimum == maximum && !_is_prime(minimum)) @@ -128,21 +109,23 @@ class Multi_field_operators // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; // } } + /** * @brief Returns the current characteristics as the product of all of them. - * + * * @return The value of the current characteristic. */ - const Characteristic& get_characteristic() const { return productOfAllCharacteristics_; } + [[nodiscard]] const Characteristic& get_characteristic() const { return productOfAllCharacteristics_; } /** * @brief Returns the value of an element in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @param e Element to return the value from. * @return @p e modulo the current characteristic, such that the result is positive. */ - Element get_value(Element e) const { + [[nodiscard]] Element get_value(Element e) const + { get_value_inplace(e); return e; } @@ -150,10 +133,11 @@ class Multi_field_operators /** * @brief Stores in the given element the value of this element in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @param e Element to return the value from. */ - void get_value_inplace(Element& e) const { + void get_value_inplace(Element& e) const + { if (e >= productOfAllCharacteristics_ || e < -productOfAllCharacteristics_) mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); if (e < 0) e += productOfAllCharacteristics_; @@ -161,12 +145,13 @@ class Multi_field_operators /** * @brief Returns the sum of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element add(Element e1, const Element& e2) const { + [[nodiscard]] Element add(Element e1, const Element& e2) const + { add_inplace(e1, e2); return e1; } @@ -174,23 +159,25 @@ class Multi_field_operators /** * @brief Stores in the first element the sum of two given elements in the field, that is * `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void add_inplace(Element& e1, const Element& e2) const { + void add_inplace(Element& e1, const Element& e2) const + { e1 += e2; get_value_inplace(e1); } /** * @brief Returns the subtraction in the field of the first element by the second element. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element subtract(Element e1, const Element& e2) const { + [[nodiscard]] Element subtract(Element e1, const Element& e2) const + { subtract_inplace_front(e1, e2); return e1; } @@ -198,34 +185,38 @@ class Multi_field_operators /** * @brief Stores in the first element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_front(Element& e1, const Element& e2) const { + void subtract_inplace_front(Element& e1, const Element& e2) const + { e1 -= e2; get_value_inplace(e1); } + /** * @brief Stores in the second element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_back(const Element& e1, Element& e2) const { + void subtract_inplace_back(const Element& e1, Element& e2) const + { mpz_sub(e2.get_mpz_t(), e1.get_mpz_t(), e2.get_mpz_t()); get_value_inplace(e2); } /** * @brief Returns the multiplication of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element multiply(Element e1, const Element& e2) const { + [[nodiscard]] Element multiply(Element e1, const Element& e2) const + { multiply_inplace(e1, e2); return e1; } @@ -233,24 +224,26 @@ class Multi_field_operators /** * @brief Stores in the first element the multiplication of two given elements in the field, * that is `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void multiply_inplace(Element& e1, const Element& e2) const { + void multiply_inplace(Element& e1, const Element& e2) const + { e1 *= e2; get_value_inplace(e1); } /** * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. - * + * * @param e First element. * @param m Second element. * @param a Third element. * @return `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. */ - Element multiply_and_add(Element e, const Element& m, const Element& a) const { + [[nodiscard]] Element multiply_and_add(Element e, const Element& m, const Element& a) const + { multiply_and_add_inplace_front(e, m, a); return e; } @@ -259,26 +252,29 @@ class Multi_field_operators * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the first element. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_front(Element& e, const Element& m, const Element& a) const { + void multiply_and_add_inplace_front(Element& e, const Element& m, const Element& a) const + { e *= m; e += a; get_value_inplace(e); } + /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the third element. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_back(const Element& e, const Element& m, Element& a) const { + void multiply_and_add_inplace_back(const Element& e, const Element& m, Element& a) const + { a += e * m; get_value_inplace(a); } @@ -286,13 +282,14 @@ class Multi_field_operators /** * @brief Adds the first element to the second one and multiplies the third one with it. * Returns the result in the field. - * + * * @param e First element. * @param a Second element. * @param m Third element. * @return `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. */ - Element add_and_multiply(Element e, const Element& a, const Element& m) const { + [[nodiscard]] Element add_and_multiply(Element e, const Element& a, const Element& m) const + { add_and_multiply_inplace_front(e, a, m); return e; } @@ -301,39 +298,43 @@ class Multi_field_operators * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the first element. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_front(Element& e, const Element& a, const Element& m) const { + void add_and_multiply_inplace_front(Element& e, const Element& a, const Element& m) const + { e += a; e *= m; get_value_inplace(e); } + /** * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the third element. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_back(const Element& e, const Element& a, Element& m) const { + void add_and_multiply_inplace_back(const Element& e, const Element& a, Element& m) const + { m *= e + a; get_value_inplace(m); } /** * @brief Returns true if the two given elements are equal in the field, false otherwise. - * + * * @param e1 First element to compare. * @param e2 Second element to compare. * @return true If `e1 % productOfAllCharacteristics == e2 % productOfAllCharacteristics`. * @return false Otherwise. */ - bool are_equal(const Element& e1, const Element& e2) const { + bool are_equal(const Element& e1, const Element& e2) const + { if (e1 == e2) return true; return get_value(e1) == get_value(e2); } @@ -341,23 +342,27 @@ class Multi_field_operators /** * @brief Returns the inverse of the given element in the sense of @cite boissonnat:hal-00922572 with respect * to the product of all characteristics. - * + * * @param e Element to get the inverse from. * @return Inverse in the current field. */ - Element get_inverse(const Element& e) const { + [[nodiscard]] Element get_inverse(const Element& e) const + { return get_partial_inverse(e, productOfAllCharacteristics_).first; } + /** * @brief Returns the inverse of the given element in the multi-field corresponding to the given sub-product * of the product of all characteristics in the multi-field. See @cite boissonnat:hal-00922572 for more details. - * + * * @param e Element to get the inverse from. * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return Pair of the inverse of @p e and the characteristic the inverse is coming from. */ - std::pair get_partial_inverse( - const Element& e, const Characteristic& productOfCharacteristics) const { + [[nodiscard]] std::pair get_partial_inverse( + const Element& e, + const Characteristic& productOfCharacteristics) const + { Characteristic QR; mpz_gcd(QR.get_mpz_t(), e.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) @@ -377,13 +382,14 @@ class Multi_field_operators /** * @brief Returns the additive identity of a field. - * + * * @return The additive identity of a field. */ static const Element& get_additive_identity() { return additiveID_; } + /** * @brief Returns the multiplicative identity of a field. - * + * * @return The multiplicative identity of a field. */ static const Element& get_multiplicative_identity() { return multiplicativeID_; } @@ -391,12 +397,13 @@ class Multi_field_operators /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. - * + * * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return The partial multiplicative identity of the multi-field. */ - Element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) const { - if (productOfCharacteristics == 0) { + [[nodiscard]] Element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) const + { + if (productOfCharacteristics == nullCharacteristic) { return get_multiplicative_identity(); } Element multIdentity(0); @@ -409,35 +416,25 @@ class Multi_field_operators return multIdentity; } - // static constexpr bool handles_only_z2() { return false; } - - /** - * @brief Assign operator. - */ - Multi_field_operators& operator=(Multi_field_operators other) { - primes_.swap(other.primes_); - productOfAllCharacteristics_ = other.productOfAllCharacteristics_; - partials_.swap(other.partials_); - - return *this; - } /** * @brief Swap operator. */ - friend void swap(Multi_field_operators& f1, Multi_field_operators& f2) { + friend void swap(Multi_field_operators& f1, Multi_field_operators& f2) noexcept + { f1.primes_.swap(f2.primes_); std::swap(f1.productOfAllCharacteristics_, f2.productOfAllCharacteristics_); f1.partials_.swap(f2.partials_); } private: - std::vector primes_; /**< All characteristics. */ + std::vector primes_; /**< All characteristics. */ Characteristic productOfAllCharacteristics_; /**< Product of all characteristics. */ std::vector partials_; /**< Partial products of the characteristics. */ inline static const Element multiplicativeID_ = 1; inline static const Element additiveID_ = 0; - static constexpr bool _is_prime(const int p) { + static constexpr bool _is_prime(const int p) + { if (p <= 1) return false; if (p <= 3) return true; if (p % 2 == 0 || p % 3 == 0) return false; diff --git a/multipers/gudhi/gudhi/Fields/Multi_field_shared.h b/multipers/gudhi/gudhi/Fields/Multi_field_shared.h index dcf5d0d2..05ebbecb 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field_shared.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field_shared.h @@ -31,79 +31,125 @@ namespace persistence_fields { * * @brief Class representing an element of a multi-field. If each instantiation of the class can represent another * element, they all share the same characteristics. That is if the characteristics are set for one, they will be - * set for all the others. The characteristics can be set before instantiating the elements with the static + * set for all the others. The characteristics can be set before instantiating the elements with the static * @ref Shared_multi_field_element::initialize method. */ -class Shared_multi_field_element +class Shared_multi_field_element { public: - using Element = mpz_class; /**< Type for the elements in the field. */ + using Element = mpz_class; /**< Type for the elements in the field. */ using Characteristic = Element; /**< Type for the field characteristic. */ /** * @brief Default constructor. Sets the element to 0. */ - Shared_multi_field_element(); + Shared_multi_field_element() : element_(0) {} + /** * @brief Constructor setting the element to the given value. - * + * * @param element Value of the element. */ - Shared_multi_field_element(Element element); - /** - * @brief Copy constructor. - * - * @param toCopy Element to copy. - */ - Shared_multi_field_element(const Shared_multi_field_element& toCopy); - /** - * @brief Move constructor. - * - * @param toMove Element to move. - */ - Shared_multi_field_element(Shared_multi_field_element&& toMove) noexcept; + Shared_multi_field_element(Element element) : element_(std::move(element)) + { + mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } /** * @brief Initialize the multi-field to the characteristics (primes) contained in the given interval. * Should be called first before constructing the field elements. - * + * * @param minimum Lowest value in the interval. * @param maximum Highest value in the interval. */ - static void initialize(unsigned int minimum, unsigned int maximum); + static void initialize(unsigned int minimum, unsigned int maximum) + { + if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); + if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); + if (minimum == maximum && !_is_prime(minimum)) + throw std::invalid_argument("The given interval does not contain a prime number."); + + unsigned int curr_prime = minimum; + mpz_t tmp_prime; + mpz_init_set_ui(tmp_prime, minimum); + // test if min_prime is prime + int is_prime = mpz_probab_prime_p(tmp_prime, 25); // probabilistic primality test + + if (is_prime == 0) { // min_prime is composite + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + + primes_.clear(); + while (curr_prime <= maximum) { + primes_.push_back(curr_prime); + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + mpz_clear(tmp_prime); + + if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); + + productOfAllCharacteristics_ = 1; + for (const unsigned int p : primes_) { + productOfAllCharacteristics_ *= p; + } + + partials_.resize(primes_.size()); + for (unsigned int i = 0; i < primes_.size(); ++i) { + unsigned int p = primes_[i]; + partials_[i] = productOfAllCharacteristics_ / p; + mpz_powm_ui(partials_[i].get_mpz_t(), partials_[i].get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); + } + + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. + // for (unsigned int i = 0; i < partials_.size(); ++i){ + // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; + // } + } /** * @brief operator+= */ - friend void operator+=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + friend void operator+=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) + { f1.element_ += f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator+ */ - friend Shared_multi_field_element operator+(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + friend Shared_multi_field_element operator+(Shared_multi_field_element f1, Shared_multi_field_element const& f2) + { f1 += f2; return f1; } + /** * @brief operator+= */ - friend void operator+=(Shared_multi_field_element& f, Element const v) { + friend void operator+=(Shared_multi_field_element& f, const Element& v) + { f.element_ += v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator+ */ - friend Shared_multi_field_element operator+(Shared_multi_field_element f, Element const v) { + friend Shared_multi_field_element operator+(Shared_multi_field_element f, const Element& v) + { f += v; return f; } + /** * @brief operator+ */ - friend Element operator+(Element v, Shared_multi_field_element const& f) { + friend Element operator+(Element v, Shared_multi_field_element const& f) + { v += f.element_; mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return v; @@ -112,35 +158,44 @@ class Shared_multi_field_element /** * @brief operator-= */ - friend void operator-=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + friend void operator-=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) + { f1.element_ -= f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator- */ - friend Shared_multi_field_element operator-(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + friend Shared_multi_field_element operator-(Shared_multi_field_element f1, Shared_multi_field_element const& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= */ - friend void operator-=(Shared_multi_field_element& f, Element const v) { + friend void operator-=(Shared_multi_field_element& f, const Element& v) + { f.element_ -= v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator- */ - friend Shared_multi_field_element operator-(Shared_multi_field_element f, Element const v) { + friend Shared_multi_field_element operator-(Shared_multi_field_element f, const Element& v) + { f -= v; return f; } + /** * @brief operator- */ - friend Element operator-(Element v, Shared_multi_field_element const& f) { + friend Element operator-(Element v, Shared_multi_field_element const& f) + { if (v >= productOfAllCharacteristics_) mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); if (f.element_ > v) v += productOfAllCharacteristics_; @@ -151,35 +206,44 @@ class Shared_multi_field_element /** * @brief operator*= */ - friend void operator*=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + friend void operator*=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) + { f1.element_ *= f2.element_; mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator* */ - friend Shared_multi_field_element operator*(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + friend Shared_multi_field_element operator*(Shared_multi_field_element f1, Shared_multi_field_element const& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= */ - friend void operator*=(Shared_multi_field_element& f, Element const v) { + friend void operator*=(Shared_multi_field_element& f, const Element& v) + { f.element_ *= v; mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); } + /** * @brief operator* */ - friend Shared_multi_field_element operator*(Shared_multi_field_element f, Element const v) { + friend Shared_multi_field_element operator*(Shared_multi_field_element f, const Element& v) + { f *= v; return f; } + /** * @brief operator* */ - friend Element operator*(Element v, Shared_multi_field_element const& f) { + friend Element operator*(Element v, Shared_multi_field_element const& f) + { v *= f.element_; mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return v; @@ -188,37 +252,46 @@ class Shared_multi_field_element /** * @brief operator== */ - friend bool operator==(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) { + friend bool operator==(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) + { return f1.element_ == f2.element_; } + /** * @brief operator== */ - friend bool operator==(const Element& v, const Shared_multi_field_element& f) { + friend bool operator==(const Element& v, const Shared_multi_field_element& f) + { if (v < productOfAllCharacteristics_) return v == f.element_; Element e(v); mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return e == f.element_; } + /** * @brief operator== */ - friend bool operator==(const Shared_multi_field_element& f, const Element& v) { + friend bool operator==(const Shared_multi_field_element& f, const Element& v) + { if (v < productOfAllCharacteristics_) return v == f.element_; Element e(v); mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); return e == f.element_; } + /** * @brief operator!= */ - friend bool operator!=(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) { + friend bool operator!=(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) + { return !(f1 == f2); } + /** * @brief operator!= */ friend bool operator!=(const Element& v, const Shared_multi_field_element& f) { return !(v == f); } + /** * @brief operator!= */ @@ -227,222 +300,135 @@ class Shared_multi_field_element /** * @brief Assign operator. */ - Shared_multi_field_element& operator=(Shared_multi_field_element other); - /** - * @brief Assign operator. - */ - Shared_multi_field_element& operator=(const Element& value); + Shared_multi_field_element& operator=(const Element& value) + { + mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return *this; + } + /** * @brief Swap operator. */ - friend void swap(Shared_multi_field_element& f1, Shared_multi_field_element& f2) { + friend void swap(Shared_multi_field_element& f1, Shared_multi_field_element& f2) noexcept + { std::swap(f1.element_, f2.element_); } /** * @brief Casts the element into an unsigned int. */ - operator unsigned int() const; + operator unsigned int() const { return element_.get_ui(); } + /** * @brief Casts the element into a mpz_class. */ - operator mpz_class() const; + explicit operator mpz_class() const { return element_; } /** * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. - * + * * @return The inverse. */ - Shared_multi_field_element get_inverse() const; + [[nodiscard]] Shared_multi_field_element get_inverse() const + { + return get_partial_inverse(productOfAllCharacteristics_).first; + } + /** * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, * see @cite boissonnat:hal-00922572. - * + * * @param productOfCharacteristics Sub-product of the characteristics. * @return Pair of the inverse and the characteristic the inverse corresponds to. */ - std::pair get_partial_inverse( - const Characteristic& productOfCharacteristics) const; + [[nodiscard]] std::pair get_partial_inverse( + const Characteristic& productOfCharacteristics) const + { + Element QR; + mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) + + if (QR == productOfCharacteristics) + return {Shared_multi_field_element(), multiplicativeID_}; // partial inverse is 0 + + Element QT = productOfCharacteristics / QR; + + Element inv_qt; + mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; + } /** * @brief Returns the additive identity of a field. - * + * * @return The additive identity of a field. */ - static Shared_multi_field_element get_additive_identity(); + static Shared_multi_field_element get_additive_identity() { return {}; } + /** * @brief Returns the multiplicative identity of a field. - * + * * @return The multiplicative identity of a field. */ - static Shared_multi_field_element get_multiplicative_identity(); + static Shared_multi_field_element get_multiplicative_identity() { return {multiplicativeID_}; } + /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. - * + * * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return The partial multiplicative identity of the multi-field. */ - static Shared_multi_field_element get_partial_multiplicative_identity( - const Characteristic& productOfCharacteristics); + static Shared_multi_field_element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) + { + if (productOfCharacteristics == 0) { + return {multiplicativeID_}; + } + Shared_multi_field_element mult; + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; + } + /** * @brief Returns the product of all characteristics. - * + * * @return The product of all characteristics. */ - static Characteristic get_characteristic(); + static Characteristic get_characteristic() { return productOfAllCharacteristics_; } /** * @brief Returns the value of the element. - * + * * @return Value of the element. */ - Element get_value() const; - - // static constexpr bool handles_only_z2() { return false; } + [[nodiscard]] Element get_value() const { return element_; } private: - Element element_; /**< Element. */ - static inline std::vector primes_; /**< All characteristics. */ + Element element_; /**< Element. */ + static inline std::vector primes_; /**< All characteristics. */ static inline Characteristic productOfAllCharacteristics_ = 0; /**< Product of all characteristics. */ - static inline std::vector partials_; /**< Partial products of the characteristics. */ - static inline const Element multiplicativeID_ = 1; /**< Multiplicative identity. */ - - static constexpr bool _is_prime(const int p); -}; - -inline Shared_multi_field_element::Shared_multi_field_element() : element_(0) {} - -inline Shared_multi_field_element::Shared_multi_field_element(Element element) : element_(element) { - mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); -} + static inline std::vector partials_; /**< Partial products of the characteristics. */ + static inline const Element multiplicativeID_ = 1; /**< Multiplicative identity. */ -inline Shared_multi_field_element::Shared_multi_field_element(const Shared_multi_field_element& toCopy) - : element_(toCopy.element_) {} + static constexpr bool _is_prime(const unsigned int p) + { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; -inline Shared_multi_field_element::Shared_multi_field_element(Shared_multi_field_element&& toMove) noexcept - : element_(std::move(toMove.element_)) {} + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; -inline void Shared_multi_field_element::initialize(unsigned int minimum, unsigned int maximum) { - if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); - if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); - if (minimum == maximum && !_is_prime(minimum)) - throw std::invalid_argument("The given interval does not contain a prime number."); - - unsigned int curr_prime = minimum; - mpz_t tmp_prime; - mpz_init_set_ui(tmp_prime, minimum); - // test if min_prime is prime - int is_prime = mpz_probab_prime_p(tmp_prime, 25); // probabilistic primality test - - if (is_prime == 0) { // min_prime is composite - mpz_nextprime(tmp_prime, tmp_prime); - curr_prime = mpz_get_ui(tmp_prime); - } - - primes_.clear(); - while (curr_prime <= maximum) { - primes_.push_back(curr_prime); - mpz_nextprime(tmp_prime, tmp_prime); - curr_prime = mpz_get_ui(tmp_prime); + return true; } - mpz_clear(tmp_prime); - - if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); - - productOfAllCharacteristics_ = 1; - for (const unsigned int p : primes_) { - productOfAllCharacteristics_ *= p; - } - - partials_.resize(primes_.size()); - for (unsigned int i = 0; i < primes_.size(); ++i) { - unsigned int p = primes_[i]; - partials_[i] = productOfAllCharacteristics_ / p; - mpz_powm_ui(partials_[i].get_mpz_t(), partials_[i].get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); - } - - // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, - // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. - // for (unsigned int i = 0; i < partials_.size(); ++i){ - // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; - // } -} - -inline Shared_multi_field_element& Shared_multi_field_element::operator=(Shared_multi_field_element other) { - std::swap(element_, other.element_); - return *this; -} - -inline Shared_multi_field_element& Shared_multi_field_element::operator=(const Element& value) { - mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); - return *this; -} - -inline Shared_multi_field_element::operator unsigned int() const { return element_.get_ui(); } - -inline Shared_multi_field_element::operator mpz_class() const { return element_; } - -inline Shared_multi_field_element Shared_multi_field_element::get_inverse() const { - return get_partial_inverse(productOfAllCharacteristics_).first; -} - -inline std::pair -Shared_multi_field_element::get_partial_inverse(const Characteristic& productOfCharacteristics) const { - Element QR; - mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) - - if (QR == productOfCharacteristics) return {Shared_multi_field_element(), multiplicativeID_}; // partial inverse is 0 - - Element QT = productOfCharacteristics / QR; - - Element inv_qt; - mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); - - auto res = get_partial_multiplicative_identity(QT); - res *= inv_qt; - - return {res, QT}; -} - -inline Shared_multi_field_element Shared_multi_field_element::get_additive_identity() { - return Shared_multi_field_element(); -} - -inline Shared_multi_field_element Shared_multi_field_element::get_multiplicative_identity() { - return Shared_multi_field_element(multiplicativeID_); -} - -inline Shared_multi_field_element Shared_multi_field_element::get_partial_multiplicative_identity( - const Characteristic& productOfCharacteristics) { - if (productOfCharacteristics == 0) { - return Shared_multi_field_element(multiplicativeID_); - } - Shared_multi_field_element mult; - for (unsigned int idx = 0; idx < primes_.size(); ++idx) { - if ((productOfCharacteristics % primes_[idx]) == 0) { - mult += partials_[idx]; - } - } - return mult; -} - -inline Shared_multi_field_element::Characteristic Shared_multi_field_element::get_characteristic() { - return productOfAllCharacteristics_; -} - -inline Shared_multi_field_element::Element Shared_multi_field_element::get_value() const { return element_; } - -inline constexpr bool Shared_multi_field_element::_is_prime(const int p) { - if (p <= 1) return false; - if (p <= 3) return true; - if (p % 2 == 0 || p % 3 == 0) return false; - - for (long i = 5; i * i <= p; i = i + 6) - if (p % i == 0 || p % (i + 2) == 0) return false; - - return true; -} +}; } // namespace persistence_fields } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Fields/Multi_field_small.h b/multipers/gudhi/gudhi/Fields/Multi_field_small.h index afef216a..49098865 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field_small.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field_small.h @@ -38,94 +38,95 @@ namespace persistence_fields { * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. * Will be used as the field element type. */ -template > > -class Multi_field_element_with_small_characteristics { +class Multi_field_element_with_small_characteristics +{ public: using Element = Unsigned_integer_type; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Characteristic = Element; /**< Type for the field characteristic. */ template using isInteger = std::enable_if_t >; /** * @brief Default constructor. Sets the element to 0. */ - Multi_field_element_with_small_characteristics() : element_(0) { + Multi_field_element_with_small_characteristics() : element_(0) + { static_assert(maximum >= 2, "Characteristics have to be positive."); static_assert(minimum <= maximum, "The given interval is not valid."); static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); static_assert(productOfAllCharacteristics_ != 1, "The given interval does not contain a prime number."); } + /** * @brief Constructor setting the element to the given value. * * @param element Value of the element. */ template > - Multi_field_element_with_small_characteristics(Integer_type element) - : element_(_get_value(element)) { + Multi_field_element_with_small_characteristics(Integer_type element) : element_(_get_value(element)) + { static_assert(maximum >= 2, "Characteristics has to be positive."); static_assert(minimum <= maximum, "The given interval is not valid."); static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); static_assert(productOfAllCharacteristics_ != 1, "The given interval does not contain a prime number."); } - /** - * @brief Copy constructor. - * - * @param toCopy Element to copy. - */ - Multi_field_element_with_small_characteristics(const Multi_field_element_with_small_characteristics& toCopy) - : element_(toCopy.element_) {} - /** - * @brief Move constructor. - * - * @param toMove Element to move. - */ - Multi_field_element_with_small_characteristics(Multi_field_element_with_small_characteristics&& toMove) noexcept - : element_(std::exchange(toMove.element_, 0)) {} /** * @brief operator+= */ friend void operator+=(Multi_field_element_with_small_characteristics& f1, - Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _add(f1.element_, f2.element_); } + /** * @brief operator+ */ friend Multi_field_element_with_small_characteristics operator+( - Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics f1, + Multi_field_element_with_small_characteristics const& f2) + { f1 += f2; return f1; } + /** * @brief operator+= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator+=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator+=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _add(f.element_, _get_value(v)); } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Multi_field_element_with_small_characteristics operator+(Multi_field_element_with_small_characteristics f, - const Integer_type& v) { + const Integer_type& v) + { f += v; return f; } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator+(const Integer_type& v, Multi_field_element_with_small_characteristics f) { + friend Integer_type operator+(const Integer_type& v, Multi_field_element_with_small_characteristics f) + { f += v; return f.element_; } @@ -134,44 +135,54 @@ class Multi_field_element_with_small_characteristics { * @brief operator-= */ friend void operator-=(Multi_field_element_with_small_characteristics& f1, - Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _subtract(f1.element_, f2.element_); } + /** * @brief operator- */ friend Multi_field_element_with_small_characteristics operator-( - Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics f1, + Multi_field_element_with_small_characteristics const& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator-=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator-=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _subtract(f.element_, _get_value(v)); } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Multi_field_element_with_small_characteristics operator-(Multi_field_element_with_small_characteristics f, - const Integer_type& v) { + const Integer_type& v) + { f -= v; return f; } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator-(const Integer_type& v, const Multi_field_element_with_small_characteristics& f) { + friend Integer_type operator-(const Integer_type& v, const Multi_field_element_with_small_characteristics& f) + { return _subtract(_get_value(v), f.element_); } @@ -179,44 +190,54 @@ class Multi_field_element_with_small_characteristics { * @brief operator*= */ friend void operator*=(Multi_field_element_with_small_characteristics& f1, - Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _multiply(f1.element_, f2.element_); } + /** * @brief operator* */ friend Multi_field_element_with_small_characteristics operator*( - Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + Multi_field_element_with_small_characteristics f1, + Multi_field_element_with_small_characteristics const& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator*=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator*=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _multiply(f.element_, _get_value(v)); } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Multi_field_element_with_small_characteristics operator*(Multi_field_element_with_small_characteristics f, - const Integer_type& v) { + const Integer_type& v) + { f *= v; return f; } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator*(const Integer_type& v, Multi_field_element_with_small_characteristics f) { + friend Integer_type operator*(const Integer_type& v, Multi_field_element_with_small_characteristics f) + { f *= v; return f.element_; } @@ -225,75 +246,82 @@ class Multi_field_element_with_small_characteristics { * @brief operator== */ friend bool operator==(const Multi_field_element_with_small_characteristics& f1, - const Multi_field_element_with_small_characteristics& f2) { + const Multi_field_element_with_small_characteristics& f2) + { return f1.element_ == f2.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Integer_type v, const Multi_field_element_with_small_characteristics& f) { + friend bool operator==(const Integer_type v, const Multi_field_element_with_small_characteristics& f) + { return _get_value(v) == f.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Multi_field_element_with_small_characteristics& f, const Integer_type v) { + friend bool operator==(const Multi_field_element_with_small_characteristics& f, const Integer_type v) + { return _get_value(v) == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Multi_field_element_with_small_characteristics& f1, - const Multi_field_element_with_small_characteristics& f2) { + const Multi_field_element_with_small_characteristics& f2) + { return !(f1 == f2); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Integer_type v, const Multi_field_element_with_small_characteristics& f) { + friend bool operator!=(const Integer_type v, const Multi_field_element_with_small_characteristics& f) + { return !(v == f); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Multi_field_element_with_small_characteristics& f, const Integer_type v) { + friend bool operator!=(const Multi_field_element_with_small_characteristics& f, const Integer_type v) + { return !(v == f); } /** * @brief Assign operator. - */ - Multi_field_element_with_small_characteristics& operator=(Multi_field_element_with_small_characteristics other) { - std::swap(element_, other.element_); - return *this; - } - /** - * @brief Assign operator. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - Multi_field_element_with_small_characteristics& operator=(const Integer_type& value) { + Multi_field_element_with_small_characteristics& operator=(const Integer_type& value) + { element_ = _get_value(value); return *this; } + /** * @brief Swap operator. */ friend void swap(Multi_field_element_with_small_characteristics& f1, - Multi_field_element_with_small_characteristics& f2) { + Multi_field_element_with_small_characteristics& f2) noexcept + { std::swap(f1.element_, f2.element_); } @@ -307,9 +335,11 @@ class Multi_field_element_with_small_characteristics { * * @return The inverse. */ - Multi_field_element_with_small_characteristics get_inverse() const { + Multi_field_element_with_small_characteristics get_inverse() const + { return get_partial_inverse(productOfAllCharacteristics_).first; } + /** * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, * see @cite boissonnat:hal-00922572. @@ -317,8 +347,9 @@ class Multi_field_element_with_small_characteristics { * @param productOfCharacteristics Sub-product of the characteristics. * @return Pair of the inverse and the characteristic the inverse corresponds to. */ - std::pair get_partial_inverse( - Characteristic productOfCharacteristics) const { + std::pair get_partial_inverse( + Characteristic productOfCharacteristics) const + { Characteristic gcd = std::gcd(element_, productOfAllCharacteristics_); if (gcd == productOfCharacteristics) @@ -339,17 +370,21 @@ class Multi_field_element_with_small_characteristics { * * @return The additive identity of a field. */ - static Multi_field_element_with_small_characteristics get_additive_identity() { + static Multi_field_element_with_small_characteristics get_additive_identity() + { return Multi_field_element_with_small_characteristics(); } + /** * @brief Returns the multiplicative identity of a field. * * @return The multiplicative identity of a field. */ - static Multi_field_element_with_small_characteristics get_multiplicative_identity() { + static Multi_field_element_with_small_characteristics get_multiplicative_identity() + { return Multi_field_element_with_small_characteristics(multiplicativeID_); } + /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. @@ -358,7 +393,8 @@ class Multi_field_element_with_small_characteristics { * @return The partial multiplicative identity of the multi-field. */ static Multi_field_element_with_small_characteristics get_partial_multiplicative_identity( - const Characteristic& productOfCharacteristics) { + const Characteristic& productOfCharacteristics) + { if (productOfCharacteristics == 0) { return Multi_field_element_with_small_characteristics(multiplicativeID_); } @@ -370,6 +406,7 @@ class Multi_field_element_with_small_characteristics { } return mult; } + /** * @brief Returns the product of all characteristics. * @@ -384,10 +421,9 @@ class Multi_field_element_with_small_characteristics { */ Element get_value() const { return element_; } - // static constexpr bool handles_only_z2() { return false; } - private: - static constexpr bool _is_prime(const unsigned int p) { + static constexpr bool _is_prime(const unsigned int p) + { if (p <= 1) return false; if (p <= 3) return true; if (p % 2 == 0 || p % 3 == 0) return false; @@ -397,7 +433,9 @@ class Multi_field_element_with_small_characteristics { return true; } - static constexpr Element _multiply(Element a, Element b) { + + static constexpr Element _multiply(Element a, Element b) + { Element res = 0; Element temp_b = 0; @@ -418,7 +456,9 @@ class Multi_field_element_with_small_characteristics { } return res; } - static constexpr Element _add(Element element, Element v) { + + static constexpr Element _add(Element element, Element v) + { if (UINT_MAX - element < v) { // automatic unsigned integer overflow behaviour will make it work element += v; @@ -431,7 +471,9 @@ class Multi_field_element_with_small_characteristics { return element; } - static constexpr Element _subtract(Element element, Element v) { + + static constexpr Element _subtract(Element element, Element v) + { if (element < v) { element += productOfAllCharacteristics_; } @@ -439,7 +481,9 @@ class Multi_field_element_with_small_characteristics { return element; } - static constexpr int _get_inverse(Element element, const Element mod) { + + static constexpr int _get_inverse(Element element, const Element mod) + { // to solve: Ax + My = 1 int M = mod; int A = element; @@ -462,8 +506,9 @@ class Multi_field_element_with_small_characteristics { } template > - static constexpr Element _get_value(Integer_type e) { - if constexpr (std::is_signed_v){ + static constexpr Element _get_value(Integer_type e) + { + if constexpr (std::is_signed_v) { if (e < -static_cast(productOfAllCharacteristics_)) e = e % productOfAllCharacteristics_; if (e < 0) return e += productOfAllCharacteristics_; return e < static_cast(productOfAllCharacteristics_) ? e : e % productOfAllCharacteristics_; @@ -482,7 +527,7 @@ class Multi_field_element_with_small_characteristics { } return res; }(); - static inline constexpr Characteristic productOfAllCharacteristics_ = []() { + static constexpr Characteristic productOfAllCharacteristics_ = []() { Characteristic res = 1; for (Characteristic i = minimum; i <= maximum; ++i) { if (_is_prime(i)) { @@ -515,7 +560,7 @@ class Multi_field_element_with_small_characteristics { }(); // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, // multiplicativeID_ is computed (see commented lambda function below). TODO: verify with Clement. - static inline constexpr Element multiplicativeID_ = 1; /*= [](){ + static constexpr Element multiplicativeID_ = 1; /*= [](){ unsigned int res = 0; for (unsigned int i = 0; i < partials_.size(); ++i){ res = (res + partials_[i]) % productOfAllCharacteristics_; diff --git a/multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h b/multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h index bd22fedd..94fbb22d 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field_small_operators.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -34,61 +34,44 @@ namespace persistence_fields { * @brief Class defining operators for a multi-field with "consecutive" characteristic range, such that * `productOfAllCharacteristics ^ 2` fits into an unsigned int. */ -class Multi_field_operators_with_small_characteristics +class Multi_field_operators_with_small_characteristics { public: - using Element = unsigned int; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Element = unsigned int; /**< Type for the elements in the field. */ + using Characteristic = Element; /**< Type for the field characteristic. */ + + inline static const Characteristic nullCharacteristic = 0; /**< Value of a non initialized characteristic. */ /** * @brief Default constructor, sets the product of all characteristics to 0. */ - Multi_field_operators_with_small_characteristics() : productOfAllCharacteristics_(0) /* , multiplicativeID_(1) */ + Multi_field_operators_with_small_characteristics() + : productOfAllCharacteristics_(nullCharacteristic) /* , multiplicativeID_(1) */ {} + /** * @brief Constructor setting the characteristics to all prime numbers between the two given integers. * The product of all primes to the square has to fit into an unsigned int. - * + * * @param minCharacteristic Smallest value of a prime. * @param maxCharacteristic Highest value of a prime. */ Multi_field_operators_with_small_characteristics(int minCharacteristic, int maxCharacteristic) - : productOfAllCharacteristics_(0) //, multiplicativeID_(1) + : productOfAllCharacteristics_(nullCharacteristic) //, multiplicativeID_(1) { set_characteristic(minCharacteristic, maxCharacteristic); } - /** - * @brief Copy constructor. - * - * @param toCopy Operators to copy. - */ - Multi_field_operators_with_small_characteristics(const Multi_field_operators_with_small_characteristics& toCopy) - : primes_(toCopy.primes_), - productOfAllCharacteristics_(toCopy.productOfAllCharacteristics_), - partials_(toCopy.partials_) /* , - multiplicativeID_(toCopy.multiplicativeID_) */ - {} - /** - * @brief Move constructor. - * - * @param toMove Operators to move. - */ - Multi_field_operators_with_small_characteristics(Multi_field_operators_with_small_characteristics&& toMove) noexcept - : primes_(std::move(toMove.primes_)), - productOfAllCharacteristics_(std::move(toMove.productOfAllCharacteristics_)), - partials_(std::move(toMove.partials_)) /* , - multiplicativeID_(std::move(toMove.multiplicativeID_)) */ - {} /** * @brief Set the characteristics of the field, which are stored in a single value as a product of all of them. * The characteristics will be all prime numbers in the given interval. * The product of all primes to the square has to fit into an unsigned int. - * + * * @param minimum Smallest value of a prime. * @param maximum Highest value of a prime. */ - void set_characteristic(int minimum, int maximum) { + void set_characteristic(int minimum, int maximum) + { if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); if (minimum == maximum && !_is_prime(minimum)) @@ -97,7 +80,7 @@ class Multi_field_operators_with_small_characteristics productOfAllCharacteristics_ = 1; primes_.clear(); for (unsigned int i = minimum; i <= static_cast(maximum); ++i) { - if (_is_prime(i)) { + if (_is_prime(static_cast(i))) { primes_.push_back(i); productOfAllCharacteristics_ *= i; } @@ -127,97 +110,107 @@ class Multi_field_operators_with_small_characteristics // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; // } } + /** * @brief Returns the current characteristics as the product of all of them. - * + * * @return The value of the current characteristic. */ - const Characteristic& get_characteristic() const { return productOfAllCharacteristics_; } + [[nodiscard]] const Characteristic& get_characteristic() const { return productOfAllCharacteristics_; } /** * @brief Returns the value of an element in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @param e Integer to return the value from. * @return @p e modulo the current characteristic, such that the result is positive. */ - Element get_value(Element e) const { + [[nodiscard]] Element get_value(Element e) const + { return e < productOfAllCharacteristics_ ? e : e % productOfAllCharacteristics_; } /** * @brief Returns the sum of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element add(Element e1, Element e2) const { + [[nodiscard]] Element add(Element e1, Element e2) const + { return _add(get_value(e1), get_value(e2), productOfAllCharacteristics_); } /** * @brief Stores in the first element the sum of two given elements in the field, that is * `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void add_inplace(Element& e1, Element e2) const { + void add_inplace(Element& e1, Element e2) const + { e1 = _add(get_value(e1), get_value(e2), productOfAllCharacteristics_); } /** * @brief Returns the subtraction in the field of the first element by the second element. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element subtract(Element e1, Element e2) const { + [[nodiscard]] Element subtract(Element e1, Element e2) const + { return _subtract(get_value(e1), get_value(e2), productOfAllCharacteristics_); } /** * @brief Stores in the first element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_front(Element& e1, Element e2) const { + void subtract_inplace_front(Element& e1, Element e2) const + { e1 = _subtract(get_value(e1), get_value(e2), productOfAllCharacteristics_); } + /** * @brief Stores in the second element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_back(Element e1, Element& e2) const { + void subtract_inplace_back(Element e1, Element& e2) const + { e2 = _subtract(get_value(e1), get_value(e2), productOfAllCharacteristics_); } /** * @brief Returns the multiplication of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. */ - Element multiply(Element e1, Element e2) const { + [[nodiscard]] Element multiply(Element e1, Element e2) const + { return _multiply(get_value(e1), get_value(e2), productOfAllCharacteristics_); } /** * @brief Stores in the first element the multiplication of two given elements in the field, * that is `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void multiply_inplace(Element& e1, Element e2) const { + void multiply_inplace(Element& e1, Element e2) const + { e1 = _multiply(get_value(e1), get_value(e2), productOfAllCharacteristics_); } @@ -225,13 +218,13 @@ class Multi_field_operators_with_small_characteristics * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. * @return `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. */ - Element multiply_and_add(Element e, Element m, Element a) const { return get_value(e * m + a); } + [[nodiscard]] Element multiply_and_add(Element e, Element m, Element a) const { return get_value((e * m) + a); } /** * @brief Multiplies the first element with the second one and adds the third one, that is @@ -239,41 +232,38 @@ class Multi_field_operators_with_small_characteristics * Stores the result in the first element. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_front(Element& e, Element m, Element a) const { - e = get_value(e * m + a); - } + void multiply_and_add_inplace_front(Element& e, Element m, Element a) const { e = get_value((e * m) + a); } + /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the third element. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_back(Element e, Element m, Element& a) const { - a = get_value(e * m + a); - } + void multiply_and_add_inplace_back(Element e, Element m, Element& a) const { a = get_value((e * m) + a); } /** * @brief Adds the first element to the second one and multiplies the third one with it. * Returns the result in the field. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. * @return `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. */ - Element add_and_multiply(Element e, Element a, Element m) const { return get_value((e + a) * m); } + [[nodiscard]] Element add_and_multiply(Element e, Element a, Element m) const { return get_value((e + a) * m); } /** * @brief Adds the first element to the second one and multiplies the third one with it, that is @@ -281,59 +271,59 @@ class Multi_field_operators_with_small_characteristics * Stores the result in the first element. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_front(Element& e, Element a, Element m) const { - e = get_value((e + a) * m); - } + void add_and_multiply_inplace_front(Element& e, Element a, Element m) const { e = get_value((e + a) * m); } + /** * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. * Stores the result in the third element. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_back(Element e, Element a, Element& m) const { - m = get_value((e + a) * m); - } + void add_and_multiply_inplace_back(Element e, Element a, Element& m) const { m = get_value((e + a) * m); } /** * @brief Returns true if the two given elements are equal in the field, false otherwise. - * + * * @param e1 First element to compare. * @param e2 Second element to compare. * @return true If `e1 % productOfAllCharacteristics == e2 % productOfAllCharacteristics`. * @return false Otherwise. */ - bool are_equal(Element e1, Element e2) const { return get_value(e1) == get_value(e2); } + [[nodiscard]] bool are_equal(Element e1, Element e2) const { return get_value(e1) == get_value(e2); } /** * @brief Returns the inverse of the given element in the sense of @cite boissonnat:hal-00922572 with respect * to the product of all characteristics. - * + * * @param e Element to get the inverse from. * @return Inverse in the current field. */ - Element get_inverse(const Element& e) const { + [[nodiscard]] Element get_inverse(const Element& e) const + { return get_partial_inverse(e, productOfAllCharacteristics_).first; } + /** * @brief Returns the inverse of the given element in the multi-field corresponding to the given sub-product * of the product of all characteristics in the multi-field. See @cite boissonnat:hal-00922572 for more details. - * + * * @param e Element to get the inverse from. * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return Pair of the inverse of @p e and the characteristic the inverse is coming from. */ - std::pair get_partial_inverse( - const Element& e, const Characteristic& productOfCharacteristics) const { + std::pair get_partial_inverse(const Element& e, + const Characteristic& productOfCharacteristics) const + { Characteristic gcd = std::gcd(e, productOfAllCharacteristics_); if (gcd == productOfCharacteristics) return {0, get_multiplicative_identity()}; // partial inverse is 0 @@ -350,26 +340,29 @@ class Multi_field_operators_with_small_characteristics /** * @brief Returns the additive identity of a field. - * + * * @return The additive identity of a field. */ static constexpr Element get_additive_identity() { return 0; } + /** * @brief Returns the multiplicative identity of a field. - * + * * @return The multiplicative identity of a field. */ static constexpr Element get_multiplicative_identity() { return 1; } + // static Element get_multiplicative_identity(){ return multiplicativeID_; } /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. - * + * * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. * @return The partial multiplicative identity of the multi-field. */ - Element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) const { - if (productOfCharacteristics == 0) { + [[nodiscard]] Element get_partial_multiplicative_identity(const Characteristic& productOfCharacteristics) const + { + if (productOfCharacteristics == nullCharacteristic) { return get_multiplicative_identity(); } Element multIdentity = 0; @@ -381,125 +374,107 @@ class Multi_field_operators_with_small_characteristics return multIdentity; } - // static constexpr bool handles_only_z2() { return false; } - - /** - * @brief Assign operator. - */ - Multi_field_operators_with_small_characteristics& operator=(Multi_field_operators_with_small_characteristics other) { - primes_.swap(other.primes_); - productOfAllCharacteristics_ = other.productOfAllCharacteristics_; - partials_.swap(other.partials_); - - return *this; - } /** * @brief Swap operator. */ friend void swap(Multi_field_operators_with_small_characteristics& f1, - Multi_field_operators_with_small_characteristics& f2) { + Multi_field_operators_with_small_characteristics& f2) noexcept + { f1.primes_.swap(f2.primes_); std::swap(f1.productOfAllCharacteristics_, f2.productOfAllCharacteristics_); f1.partials_.swap(f2.partials_); } private: - std::vector primes_; /**< All characteristics. */ + std::vector primes_; /**< All characteristics. */ Characteristic productOfAllCharacteristics_; /**< Product of all characteristics. */ std::vector partials_; /**< Partial products of the characteristics. */ + // static inline constexpr unsigned int multiplicativeID_ = 1; - static Element _add(Element element, Element v, Characteristic characteristic); - static Element _subtract(Element element, Element v, Characteristic characteristic); - static Element _multiply(Element a, Element b, Characteristic characteristic); - static constexpr long int _get_inverse(Element element, Characteristic mod); - static constexpr bool _is_prime(const int p); -}; + static Element _add(Element element, Element v, Characteristic characteristic) + { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= characteristic; + return element; + } -inline Multi_field_operators_with_small_characteristics::Element -Multi_field_operators_with_small_characteristics::_add(Element element, Element v, - Characteristic characteristic) { - if (UINT_MAX - element < v) { - // automatic unsigned integer overflow behaviour will make it work element += v; - element -= characteristic; + if (element >= characteristic) element -= characteristic; + return element; } - element += v; - if (element >= characteristic) element -= characteristic; - - return element; -} + static Element _subtract(Element element, Element v, Characteristic characteristic) + { + if (element < v) { + element += characteristic; + } + element -= v; -inline Multi_field_operators_with_small_characteristics::Element -Multi_field_operators_with_small_characteristics::_subtract(Element element, Element v, - Characteristic characteristic) { - if (element < v) { - element += characteristic; + return element; } - element -= v; - return element; -} + static Element _multiply(Element a, Element b, Characteristic characteristic) + { + Element res = 0; + Element temp_b = 0; -inline Multi_field_operators_with_small_characteristics::Element -Multi_field_operators_with_small_characteristics::_multiply(Element a, Element b, - Characteristic characteristic) { - Element res = 0; - Element temp_b = 0; + if (b < a) std::swap(a, b); - if (b < a) std::swap(a, b); + while (a != 0) { + if (a & 1) { + /* Add b to res, modulo m, without overflow */ + if (b >= characteristic - res) res -= characteristic; + res += b; + } + a >>= 1; - while (a != 0) { - if (a & 1) { - /* Add b to res, modulo m, without overflow */ - if (b >= characteristic - res) res -= characteristic; - res += b; + /* Double b, modulo m */ + temp_b = b; + if (b >= characteristic - b) temp_b -= characteristic; + b += temp_b; } - a >>= 1; - - /* Double b, modulo m */ - temp_b = b; - if (b >= characteristic - b) temp_b -= characteristic; - b += temp_b; - } - return res; -} - -inline constexpr long int Multi_field_operators_with_small_characteristics::_get_inverse(Element element, - Characteristic mod) { - // to solve: Ax + My = 1 - Element M = mod; - Element A = element; - long int y = 0, x = 1; - // extended euclidean division - while (A > 1) { - int quotient = A / M; - int temp = M; - - M = A % M, A = temp; - temp = y; - - y = x - quotient * y; - x = temp; + return res; } - if (x < 0) x += mod; + static constexpr long int _get_inverse(Element element, Characteristic mod) + { + // to solve: Ax + My = 1 + Element M = mod; + Element A = element; + long int y = 0, x = 1; + // extended euclidean division + while (A > 1) { + int quotient = A / M; + int temp = M; + + M = A % M, A = temp; + temp = y; + + y = x - quotient * y; + x = temp; + } + + if (x < 0) x += mod; - return x; -} + return x; + } -inline constexpr bool Multi_field_operators_with_small_characteristics::_is_prime(const int p) { - if (p <= 1) return false; - if (p <= 3) return true; - if (p % 2 == 0 || p % 3 == 0) return false; + static constexpr bool _is_prime(const int p) + { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; - for (long i = 5; i * i <= p; i = i + 6) - if (p % i == 0 || p % (i + 2) == 0) return false; + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; - return true; -} + return true; + } +}; } // namespace persistence_fields } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h b/multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h index 3ec0c497..da1025ac 100644 --- a/multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h +++ b/multipers/gudhi/gudhi/Fields/Multi_field_small_shared.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include @@ -42,10 +42,11 @@ namespace persistence_fields { */ template > > -class Shared_multi_field_element_with_small_characteristics { +class Shared_multi_field_element_with_small_characteristics +{ public: using Element = Unsigned_integer_type; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Characteristic = Element; /**< Type for the field characteristic. */ template using isInteger = std::enable_if_t >; @@ -53,6 +54,7 @@ class Shared_multi_field_element_with_small_characteristics { * @brief Default constructor. Sets the element to 0. */ Shared_multi_field_element_with_small_characteristics() : element_(0) {} + /** * @brief Constructor setting the element to the given value. * @@ -60,23 +62,8 @@ class Shared_multi_field_element_with_small_characteristics { * @param element Value of the element. */ template > - Shared_multi_field_element_with_small_characteristics(Integer_type element) : element_(_get_value(element)) {} - /** - * @brief Copy constructor. - * - * @param toCopy Element to copy. - */ - Shared_multi_field_element_with_small_characteristics( - const Shared_multi_field_element_with_small_characteristics& toCopy) - : element_(toCopy.element_) {} - /** - * @brief Move constructor. - * - * @param toMove Element to move. - */ - Shared_multi_field_element_with_small_characteristics( - Shared_multi_field_element_with_small_characteristics&& toMove) noexcept - : element_(std::exchange(toMove.element_, 0)) {} + Shared_multi_field_element_with_small_characteristics(Integer_type element) : element_(_get_value(element)) + {} /** * @brief Initialize the multi-field to the characteristics (primes) contained in the given interval. @@ -86,7 +73,8 @@ class Shared_multi_field_element_with_small_characteristics { * @param minimum Lowest value in the interval. * @param maximum Highest value in the interval. */ - static void initialize(unsigned int minimum, unsigned int maximum) { + static void initialize(unsigned int minimum, unsigned int maximum) + { if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); if (minimum == maximum && !_is_prime(minimum)) @@ -130,45 +118,55 @@ class Shared_multi_field_element_with_small_characteristics { * @brief operator+= */ friend void operator+=(Shared_multi_field_element_with_small_characteristics& f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _add(f1.element_, f2.element_); } + /** * @brief operator+ */ friend Shared_multi_field_element_with_small_characteristics operator+( Shared_multi_field_element_with_small_characteristics f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1 += f2; return f1; } + /** * @brief operator+= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator+=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator+=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _add(f.element_, _get_value(v)); } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Shared_multi_field_element_with_small_characteristics operator+( - Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + Shared_multi_field_element_with_small_characteristics f, + const Integer_type& v) + { f += v; return f; } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator+(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) { + friend Integer_type operator+(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) + { f += v; return f.element_; } @@ -177,45 +175,55 @@ class Shared_multi_field_element_with_small_characteristics { * @brief operator-= */ friend void operator-=(Shared_multi_field_element_with_small_characteristics& f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _subtract(f1.element_, f2.element_); } + /** * @brief operator- */ friend Shared_multi_field_element_with_small_characteristics operator-( Shared_multi_field_element_with_small_characteristics f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator-=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator-=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _subtract(f.element_, _get_value(v)); } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Shared_multi_field_element_with_small_characteristics operator-( - Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + Shared_multi_field_element_with_small_characteristics f, + const Integer_type& v) + { f -= v; return f; } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator-(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) { + friend Integer_type operator-(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) + { return _subtract(_get_value(v), f.element_); } @@ -223,45 +231,55 @@ class Shared_multi_field_element_with_small_characteristics { * @brief operator*= */ friend void operator*=(Shared_multi_field_element_with_small_characteristics& f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1.element_ = _multiply(f1.element_, f2.element_); } + /** * @brief operator* */ friend Shared_multi_field_element_with_small_characteristics operator*( Shared_multi_field_element_with_small_characteristics f1, - Shared_multi_field_element_with_small_characteristics const& f2) { + Shared_multi_field_element_with_small_characteristics const& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator*=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend void operator*=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) + { f.element_ = _multiply(f.element_, _get_value(v)); } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > friend Shared_multi_field_element_with_small_characteristics operator*( - Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + Shared_multi_field_element_with_small_characteristics f, + const Integer_type& v) + { f *= v; return f; } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator*(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) { + friend Integer_type operator*(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) + { f *= v; return f.element_; } @@ -270,76 +288,82 @@ class Shared_multi_field_element_with_small_characteristics { * @brief operator== */ friend bool operator==(const Shared_multi_field_element_with_small_characteristics& f1, - const Shared_multi_field_element_with_small_characteristics& f2) { + const Shared_multi_field_element_with_small_characteristics& f2) + { return f1.element_ == f2.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) { + friend bool operator==(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) + { return _get_value(v) == f.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + friend bool operator==(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) + { return _get_value(v) == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Shared_multi_field_element_with_small_characteristics& f1, - const Shared_multi_field_element_with_small_characteristics& f2) { + const Shared_multi_field_element_with_small_characteristics& f2) + { return !(f1 == f2); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Integer_type v, const Shared_multi_field_element_with_small_characteristics& f) { + friend bool operator!=(const Integer_type v, const Shared_multi_field_element_with_small_characteristics& f) + { return !(v == f); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type v) { + friend bool operator!=(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type v) + { return !(v == f); } /** * @brief Assign operator. - */ - Shared_multi_field_element_with_small_characteristics& operator=( - Shared_multi_field_element_with_small_characteristics other) { - std::swap(element_, other.element_); - return *this; - } - /** - * @brief Assign operator. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - Shared_multi_field_element_with_small_characteristics& operator=(const Integer_type& value) { + Shared_multi_field_element_with_small_characteristics& operator=(const Integer_type& value) + { element_ = _get_value(value); return *this; } + /** * @brief Swap operator. */ friend void swap(Shared_multi_field_element_with_small_characteristics& f1, - Shared_multi_field_element_with_small_characteristics& f2) { + Shared_multi_field_element_with_small_characteristics& f2) noexcept + { std::swap(f1.element_, f2.element_); } @@ -353,9 +377,11 @@ class Shared_multi_field_element_with_small_characteristics { * * @return The inverse. */ - Shared_multi_field_element_with_small_characteristics get_inverse() const { + Shared_multi_field_element_with_small_characteristics get_inverse() const + { return get_partial_inverse(productOfAllCharacteristics_).first; } + /** * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, * see @cite boissonnat:hal-00922572. @@ -363,8 +389,9 @@ class Shared_multi_field_element_with_small_characteristics { * @param productOfCharacteristics Sub-product of the characteristics. * @return Pair of the inverse and the characteristic the inverse corresponds to. */ - std::pair get_partial_inverse( - Characteristic productOfCharacteristics) const { + std::pair get_partial_inverse( + Characteristic productOfCharacteristics) const + { Characteristic gcd = std::gcd(element_, productOfAllCharacteristics_); if (gcd == productOfCharacteristics) @@ -385,17 +412,21 @@ class Shared_multi_field_element_with_small_characteristics { * * @return The additive identity of a field. */ - static Shared_multi_field_element_with_small_characteristics get_additive_identity() { + static Shared_multi_field_element_with_small_characteristics get_additive_identity() + { return Shared_multi_field_element_with_small_characteristics(); } + /** * @brief Returns the multiplicative identity of a field. * * @return The multiplicative identity of a field. */ - static Shared_multi_field_element_with_small_characteristics get_multiplicative_identity() { + static Shared_multi_field_element_with_small_characteristics get_multiplicative_identity() + { return Shared_multi_field_element_with_small_characteristics(multiplicativeID_); } + /** * @brief Returns the partial multiplicative identity of the multi-field from the given product. * See @cite boissonnat:hal-00922572 for more details. @@ -404,7 +435,8 @@ class Shared_multi_field_element_with_small_characteristics { * @return The partial multiplicative identity of the multi-field. */ static Shared_multi_field_element_with_small_characteristics get_partial_multiplicative_identity( - const Characteristic& productOfCharacteristics) { + const Characteristic& productOfCharacteristics) + { if (productOfCharacteristics == 0) { return Shared_multi_field_element_with_small_characteristics(multiplicativeID_); } @@ -416,6 +448,7 @@ class Shared_multi_field_element_with_small_characteristics { } return mult; } + /** * @brief Returns the product of all characteristics. * @@ -430,10 +463,9 @@ class Shared_multi_field_element_with_small_characteristics { */ Element get_value() const { return element_; } - // static constexpr bool handles_only_z2() { return false; } - private: - static constexpr bool _is_prime(const unsigned int p) { + static constexpr bool _is_prime(const unsigned int p) + { if (p <= 1) return false; if (p <= 3) return true; if (p % 2 == 0 || p % 3 == 0) return false; @@ -443,7 +475,9 @@ class Shared_multi_field_element_with_small_characteristics { return true; } - static Element _multiply(Element a, Element b) { + + static Element _multiply(Element a, Element b) + { Element res = 0; Element temp_b = 0; @@ -464,7 +498,9 @@ class Shared_multi_field_element_with_small_characteristics { } return res; } - static Element _add(Element element, Element v) { + + static Element _add(Element element, Element v) + { if (UINT_MAX - element < v) { // automatic unsigned integer overflow behaviour will make it work element += v; @@ -477,7 +513,9 @@ class Shared_multi_field_element_with_small_characteristics { return element; } - static Element _subtract(Element element, Element v) { + + static Element _subtract(Element element, Element v) + { if (element < v) { element += productOfAllCharacteristics_; } @@ -485,7 +523,9 @@ class Shared_multi_field_element_with_small_characteristics { return element; } - static constexpr int _get_inverse(Element element, const Characteristic mod) { + + static constexpr int _get_inverse(Element element, const Characteristic mod) + { // to solve: Ax + My = 1 int M = mod; int A = element; @@ -508,8 +548,9 @@ class Shared_multi_field_element_with_small_characteristics { } template > - static constexpr Element _get_value(Integer_type e) { - if constexpr (std::is_signed_v){ + static constexpr Element _get_value(Integer_type e) + { + if constexpr (std::is_signed_v) { if (e < -static_cast(productOfAllCharacteristics_)) e = e % productOfAllCharacteristics_; if (e < 0) return e += productOfAllCharacteristics_; return e < static_cast(productOfAllCharacteristics_) ? e : e % productOfAllCharacteristics_; @@ -522,7 +563,7 @@ class Shared_multi_field_element_with_small_characteristics { static inline std::vector primes_; /**< All characteristics. */ static inline Characteristic productOfAllCharacteristics_; /**< Product of all characteristics. */ static inline std::vector partials_; /**< Partial products of the characteristics. */ - static inline constexpr Element multiplicativeID_ = 1; /**< Multiplicative identity. */ + static constexpr Element multiplicativeID_ = 1; /**< Multiplicative identity. */ }; } // namespace persistence_fields diff --git a/multipers/gudhi/gudhi/Fields/Z2_field.h b/multipers/gudhi/gudhi/Fields/Z2_field.h index 46672069..85593f4f 100644 --- a/multipers/gudhi/gudhi/Fields/Z2_field.h +++ b/multipers/gudhi/gudhi/Fields/Z2_field.h @@ -31,152 +31,191 @@ namespace persistence_fields { class Z2_field_element { public: - using Element = bool; /**< Type for the elements in the field. */ + using Element = bool; /**< Type for the elements in the field. */ template using isInteger = std::enable_if_t >; /** * @brief Default constructor. */ - Z2_field_element(); + Z2_field_element() : element_(false) {} + /** * @brief Constructor setting the element to the given value. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic. * @param element Value of the element. */ template > - Z2_field_element(Integer_type element); + Z2_field_element(Integer_type element) : element_(_get_value(element)) + {} + /** * @brief Copy constructor. - * + * * @param toCopy Element to copy. */ - Z2_field_element(const Z2_field_element& toCopy); + Z2_field_element(const Z2_field_element& toCopy) = default; + /** * @brief Move constructor. - * + * * @param toMove Element to move. */ - Z2_field_element(Z2_field_element&& toMove) noexcept; + Z2_field_element(Z2_field_element&& toMove) noexcept : element_(std::exchange(toMove.element_, false)) {} + + ~Z2_field_element() = default; /** * @brief operator+= */ - friend void operator+=(Z2_field_element& f1, Z2_field_element const& f2) { + friend void operator+=(Z2_field_element& f1, Z2_field_element const& f2) + { f1.element_ = (f1.element_ != f2.element_); } + /** * @brief operator+ */ - friend Z2_field_element operator+(Z2_field_element f1, Z2_field_element const& f2) { + friend Z2_field_element operator+(Z2_field_element f1, Z2_field_element const& f2) + { f1 += f2; return f1; } + /** * @brief operator+= * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend void operator+=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ != _get_value(v)); } + friend void operator+=(Z2_field_element& f, const Integer_type& v) + { + f.element_ = (f.element_ != _get_value(v)); + } + /** * @brief operator+ * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Z2_field_element operator+(Z2_field_element f, const Integer_type& v) { + friend Z2_field_element operator+(Z2_field_element f, const Integer_type& v) + { f += v; return f; } + /** * @brief operator+ * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Integer_type operator+(const Integer_type& v, const Z2_field_element& f) { + friend Integer_type operator+(const Integer_type& v, const Z2_field_element& f) + { return f.element_ != _get_value(v); } /** * @brief operator-= */ - friend void operator-=(Z2_field_element& f1, Z2_field_element const& f2) { + friend void operator-=(Z2_field_element& f1, Z2_field_element const& f2) + { f1.element_ = (f1.element_ != f2.element_); } + /** * @brief operator- */ - friend Z2_field_element operator-(Z2_field_element f1, Z2_field_element const& f2) { + friend Z2_field_element operator-(Z2_field_element f1, Z2_field_element const& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend void operator-=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ != _get_value(v)); } + friend void operator-=(Z2_field_element& f, const Integer_type& v) + { + f.element_ = (f.element_ != _get_value(v)); + } + /** * @brief operator- * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Z2_field_element operator-(Z2_field_element f, const Integer_type& v) { + friend Z2_field_element operator-(Z2_field_element f, const Integer_type& v) + { f -= v; return f; } + /** * @brief operator- * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Integer_type operator-(const Integer_type v, Z2_field_element const& f) { + friend Integer_type operator-(const Integer_type v, Z2_field_element const& f) + { return f.element_ != _get_value(v); } /** * @brief operator*= */ - friend void operator*=(Z2_field_element& f1, Z2_field_element const& f2) { + friend void operator*=(Z2_field_element& f1, Z2_field_element const& f2) + { f1.element_ = (f1.element_ && f2.element_); } + /** * @brief operator* */ - friend Z2_field_element operator*(Z2_field_element f1, Z2_field_element const& f2) { + friend Z2_field_element operator*(Z2_field_element f1, Z2_field_element const& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend void operator*=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ && _get_value(v)); } + friend void operator*=(Z2_field_element& f, const Integer_type& v) + { + f.element_ = (f.element_ && _get_value(v)); + } + /** * @brief operator* * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Z2_field_element operator*(Z2_field_element f, const Integer_type& v) { + friend Z2_field_element operator*(Z2_field_element f, const Integer_type& v) + { f *= v; return f; } + /** * @brief operator* * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend Integer_type operator*(const Integer_type& v, Z2_field_element const& f) { + friend Integer_type operator*(const Integer_type& v, Z2_field_element const& f) + { return f.element_ && _get_value(v); } @@ -184,119 +223,158 @@ class Z2_field_element * @brief operator== */ friend bool operator==(const Z2_field_element& f1, const Z2_field_element& f2) { return f1.element_ == f2.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend bool operator==(const Integer_type& v, const Z2_field_element& f) { + friend bool operator==(const Integer_type& v, const Z2_field_element& f) + { return _get_value(v) == f.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend bool operator==(const Z2_field_element& f, const Integer_type& v) { + friend bool operator==(const Z2_field_element& f, const Integer_type& v) + { return _get_value(v) == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Z2_field_element& f1, const Z2_field_element& f2) { return !(f1 == f2); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend bool operator!=(const Integer_type v, const Z2_field_element& f) { + friend bool operator!=(const Integer_type v, const Z2_field_element& f) + { return !(v == f); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. */ template > - friend bool operator!=(const Z2_field_element& f, const Integer_type v) { + friend bool operator!=(const Z2_field_element& f, const Integer_type v) + { return !(v == f); } /** * @brief Assign operator. */ - Z2_field_element& operator=(Z2_field_element other); + Z2_field_element& operator=(Z2_field_element other) + { + std::swap(element_, other.element_); + return *this; + } + + /** + * @brief Move assign operator. + */ + Z2_field_element& operator=(Z2_field_element&& other) noexcept + { + element_ = std::exchange(other.element_, false); + return *this; + } + /** * @brief Assign operator. */ - Z2_field_element& operator=(const unsigned int value); + template > + Z2_field_element& operator=(const Integer_type value) + { + element_ = _get_value(value); + return *this; + } + /** * @brief Swap operator. */ - friend void swap(Z2_field_element& f1, Z2_field_element& f2) { std::swap(f1.element_, f2.element_); } + friend void swap(Z2_field_element& f1, Z2_field_element& f2) noexcept { std::swap(f1.element_, f2.element_); } /** * @brief Casts the element into an unsigned int. */ - operator unsigned int() const; + operator unsigned int() const { return static_cast(element_); } /** * @brief Returns the inverse of the element. - * + * * @return The inverse. */ - Z2_field_element get_inverse() const; + [[nodiscard]] Z2_field_element get_inverse() const { return element_ ? Z2_field_element(1) : Z2_field_element(); } + /** * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. - * + * * @param productOfCharacteristics Some value. * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. */ - std::pair get_partial_inverse(unsigned int productOfCharacteristics) const; + [[nodiscard]] std::pair get_partial_inverse( + unsigned int productOfCharacteristics) const + { + return {get_inverse(), productOfCharacteristics}; + } /** * @brief Returns the additive identity of the field. - * + * * @return false. */ - static Z2_field_element get_additive_identity(); + static Z2_field_element get_additive_identity() { return {}; } + /** * @brief Returns the multiplicative identity of the field. - * + * * @return true. */ - static Z2_field_element get_multiplicative_identity(); + static Z2_field_element get_multiplicative_identity() { return {1}; } + /** * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. - * + * * @param productOfCharacteristics Some value. * @return true. */ - static Z2_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics); + static Z2_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics) + { + return {1}; + } + /** * @brief Returns the characteristic of the field, that is `2`. - * + * * @return 2. */ - static constexpr unsigned int get_characteristic(); + static constexpr unsigned int get_characteristic() { return 2; } /** * @brief Returns the value of the element. - * + * * @return Value of the element. */ - Element get_value() const; - - // static constexpr bool handles_only_z2() { return true; } + [[nodiscard]] Element get_value() const { return element_; } private: Element element_; template > - static constexpr Element _get_value(Integer_type e) { + static constexpr Element _get_value(Integer_type e) + { if constexpr (std::is_same_v) { return e; } else { @@ -305,50 +383,6 @@ class Z2_field_element } }; -inline Z2_field_element::Z2_field_element() : element_(false) {} - -template -inline Z2_field_element::Z2_field_element(Integer_type element) : element_(_get_value(element)) {} - -inline Z2_field_element::Z2_field_element(const Z2_field_element& toCopy) : element_(toCopy.element_) {} - -inline Z2_field_element::Z2_field_element(Z2_field_element&& toMove) noexcept - : element_(std::exchange(toMove.element_, 0)) {} - -inline Z2_field_element& Z2_field_element::operator=(Z2_field_element other) { - std::swap(element_, other.element_); - return *this; -} - -inline Z2_field_element& Z2_field_element::operator=(unsigned int const value) { - element_ = _get_value(value); - return *this; -} - -inline Z2_field_element::operator unsigned int() const { return element_; } - -inline Z2_field_element Z2_field_element::get_inverse() const { - return element_ ? Z2_field_element(1) : Z2_field_element(); -} - -inline std::pair Z2_field_element::get_partial_inverse( - unsigned int productOfCharacteristics) const { - return {get_inverse(), productOfCharacteristics}; -} - -inline Z2_field_element Z2_field_element::get_additive_identity() { return Z2_field_element(); } - -inline Z2_field_element Z2_field_element::get_multiplicative_identity() { return Z2_field_element(1); } - -inline Z2_field_element Z2_field_element::get_partial_multiplicative_identity( - [[maybe_unused]] unsigned int productOfCharacteristics) { - return Z2_field_element(1); -} - -inline constexpr unsigned int Z2_field_element::get_characteristic() { return 2; } - -inline Z2_field_element::Element Z2_field_element::get_value() const { return element_; } - } // namespace persistence_fields } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Fields/Z2_field_operators.h b/multipers/gudhi/gudhi/Fields/Z2_field_operators.h index 56be91d1..9914f57b 100644 --- a/multipers/gudhi/gudhi/Fields/Z2_field_operators.h +++ b/multipers/gudhi/gudhi/Fields/Z2_field_operators.h @@ -38,14 +38,11 @@ class Z2_field_operators template using isInteger = std::enable_if_t >; - /** - * @brief Default constructor. - */ - Z2_field_operators(){}; + constexpr static const Characteristic nullCharacteristic = 2; /** * @brief Returns the characteristic of the field, that is `2`. - * + * * @return 2. */ static constexpr Characteristic get_characteristic() { return 2; } @@ -53,13 +50,14 @@ class Z2_field_operators /** * @brief Returns the value of an integer in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. * @param e Integer to return the value from. * @return A boolean representing `e % 2`. */ template > - static Element get_value(Integer_type e) { + static Element get_value(Integer_type e) + { if constexpr (std::is_same_v) { return e; } else { @@ -69,14 +67,15 @@ class Z2_field_operators /** * @brief Returns the sum of two elements in the field. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. * @return `(e1 + e2) % 2` as a boolean. */ template > - static Element add(Unsigned_integer_type e1, Unsigned_integer_type e2) { + static Element add(Unsigned_integer_type e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { return e1 != e2; } else { @@ -87,13 +86,14 @@ class Z2_field_operators /** * @brief Stores in the first element the sum of two given elements in the field, that is * `(e1 + e2) % 2`, such that the result is positive. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. */ template > - static void add_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + static void add_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { e1 = e1 != e2; } else { @@ -103,14 +103,15 @@ class Z2_field_operators /** * @brief Returns the subtraction in the field of the first element by the second element. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. * @return `(e1 - e2) % 2` as a boolean. */ template > - static Element subtract(Unsigned_integer_type e1, Unsigned_integer_type e2) { + static Element subtract(Unsigned_integer_type e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { return e1 != e2; } else { @@ -121,29 +122,32 @@ class Z2_field_operators /** * @brief Stores in the first element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % 2`, such that the result is positive. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. */ template > - static void subtract_inplace_front(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + static void subtract_inplace_front(Unsigned_integer_type& e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { e1 = e1 != e2; } else { e1 = get_value(e1) != get_value(e2); } } + /** * @brief Stores in the second element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % 2`, such that the result is positive. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. */ template > - static void subtract_inplace_back(Unsigned_integer_type e1, Unsigned_integer_type& e2) { + static void subtract_inplace_back(Unsigned_integer_type e1, Unsigned_integer_type& e2) + { if constexpr (std::is_same_v) { e2 = e1 != e2; } else { @@ -153,14 +157,15 @@ class Z2_field_operators /** * @brief Returns the multiplication of two elements in the field. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. * @return `(e1 * e2) % 2` as a boolean. */ template > - static Element multiply(Unsigned_integer_type e1, Unsigned_integer_type e2) { + static Element multiply(Unsigned_integer_type e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { return e1 && e2; } else { @@ -171,13 +176,14 @@ class Z2_field_operators /** * @brief Stores in the first element the multiplication of two given elements in the field, * that is `(e1 * e2) % 2`, such that the result is positive. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element. * @param e2 Second element. */ template > - static void multiply_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + static void multiply_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { e1 = e1 && e2; } else { @@ -187,7 +193,7 @@ class Z2_field_operators /** * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param m Second element. @@ -195,7 +201,8 @@ class Z2_field_operators * @return `(e * m + a) % 2` as a boolean. */ template > - static Element multiply_and_add(Unsigned_integer_type e, Unsigned_integer_type m, Unsigned_integer_type a) { + static Element multiply_and_add(Unsigned_integer_type e, Unsigned_integer_type m, Unsigned_integer_type a) + { if constexpr (std::is_same_v) { return (e && m) != a; } else { @@ -206,16 +213,15 @@ class Z2_field_operators /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % 2`, such that the result is positive. Stores the result in the first element. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param m Second element. * @param a Third element. */ template > - static void multiply_and_add_inplace_front(Unsigned_integer_type& e, - Unsigned_integer_type m, - Unsigned_integer_type a) { + static void multiply_and_add_inplace_front(Unsigned_integer_type& e, Unsigned_integer_type m, Unsigned_integer_type a) + { if constexpr (std::is_same_v) { e = (e && m) != a; } else { @@ -226,16 +232,15 @@ class Z2_field_operators /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % 2`, such that the result is positive. Stores the result in the third element. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param m Second element. * @param a Third element. */ template > - static void multiply_and_add_inplace_back(Unsigned_integer_type e, - Unsigned_integer_type m, - Unsigned_integer_type& a) { + static void multiply_and_add_inplace_back(Unsigned_integer_type e, Unsigned_integer_type m, Unsigned_integer_type& a) + { if constexpr (std::is_same_v) { a = (e && m) != a; } else { @@ -246,7 +251,7 @@ class Z2_field_operators /** * @brief Adds the first element to the second one and multiplies the third one with it. * Returns the result in the field. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param a Second element. @@ -254,7 +259,8 @@ class Z2_field_operators * @return `((e + a) * m) % 2` as a boolean. */ template > - static Element add_and_multiply(Unsigned_integer_type e, Unsigned_integer_type a, Unsigned_integer_type m) { + static Element add_and_multiply(Unsigned_integer_type e, Unsigned_integer_type a, Unsigned_integer_type m) + { if constexpr (std::is_same_v) { return (e != a) && m; } else { @@ -265,31 +271,34 @@ class Z2_field_operators /** * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % 2`, such that the result is positive. Stores the result in the first element. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param a Second element. * @param m Third element. */ template > - static void add_and_multiply_inplace_front(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) { + static void add_and_multiply_inplace_front(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) + { if constexpr (std::is_same_v) { e = (e != a) && m; } else { e = add(e, a) ? get_value(m) : false; } } + /** * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % 2`, such that the result is positive. Stores the result in the third element. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e First element. * @param a Second element. * @param m Third element. */ template > - static void add_and_multiply_inplace_back(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) { + static void add_and_multiply_inplace_back(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) + { if constexpr (std::is_same_v) { m = (e != a) && m; } else { @@ -299,7 +308,7 @@ class Z2_field_operators /** * @brief Returns true if the two given elements are equal in the field, false otherwise. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e1 First element to compare. * @param e2 Second element to compare. @@ -307,7 +316,8 @@ class Z2_field_operators * @return false Otherwise. */ template > - static bool are_equal(Unsigned_integer_type e1, Unsigned_integer_type e2) { + static bool are_equal(Unsigned_integer_type e1, Unsigned_integer_type e2) + { if constexpr (std::is_same_v) { return e1 == e2; } else { @@ -317,57 +327,60 @@ class Z2_field_operators /** * @brief Returns the inverse of the given element in the field. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e Element to get the inverse from. * @return Inverse in the current field of `e % 2`. */ template > - static Element get_inverse(Unsigned_integer_type e) { + static Element get_inverse(Unsigned_integer_type e) + { if constexpr (std::is_same_v) { return e; } else { return get_value(e); } } + /** * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. - * + * * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. * @param e Element to get the inverse from. * @param productOfCharacteristics Some value. * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. */ template > - static std::pair get_partial_inverse( - Unsigned_integer_type e, Characteristic productOfCharacteristics) { + static std::pair get_partial_inverse(Unsigned_integer_type e, + Characteristic productOfCharacteristics) + { return {get_inverse(e), productOfCharacteristics}; } /** * @brief Returns the additive identity of the field. - * + * * @return false. */ static constexpr Element get_additive_identity() { return false; } + /** * @brief Returns the multiplicative identity of the field. - * + * * @return true. */ static constexpr Element get_multiplicative_identity() { return true; } + /** * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. - * + * * @param productOfCharacteristics Some value. * @return true. */ - static constexpr Element get_partial_multiplicative_identity( - [[maybe_unused]] Characteristic productOfCharacteristics) { + static constexpr Element get_partial_multiplicative_identity([[maybe_unused]] Characteristic productOfCharacteristics) + { return true; } - - // static constexpr bool handles_only_z2() { return true; } }; } // namespace persistence_fields diff --git a/multipers/gudhi/gudhi/Fields/Zp_field.h b/multipers/gudhi/gudhi/Fields/Zp_field.h index 3028ad10..e5e2216f 100644 --- a/multipers/gudhi/gudhi/Fields/Zp_field.h +++ b/multipers/gudhi/gudhi/Fields/Zp_field.h @@ -19,7 +19,7 @@ #include #include -#include +#include namespace Gudhi { namespace persistence_fields { @@ -34,12 +34,14 @@ namespace persistence_fields { * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. * Will be used as the field element type. */ -template > > -class Zp_field_element { +class Zp_field_element +{ public: using Element = Unsigned_integer_type; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Characteristic = Element; /**< Type for the field characteristic. */ template using isInteger = std::enable_if_t >; @@ -47,66 +49,81 @@ class Zp_field_element { * @brief Default constructor. Sets the element to 0. */ Zp_field_element() : element_(0) { static_assert(_is_prime(), "Characteristic has to be a prime number."); } + /** * @brief Constructor setting the element to the given value. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. * @param element Value of the element. */ template > - Zp_field_element(Integer_type element) : element_(_get_value(element)) { + Zp_field_element(Integer_type element) : element_(_get_value(element)) + { static_assert(_is_prime(), "Characteristic has to be a prime number."); } + /** * @brief Copy constructor. - * + * * @param toCopy Element to copy. */ - Zp_field_element(const Zp_field_element& toCopy) : element_(toCopy.element_) {} + Zp_field_element(const Zp_field_element& toCopy) = default; + /** * @brief Move constructor. - * + * * @param toMove Element to move. */ Zp_field_element(Zp_field_element&& toMove) noexcept : element_(std::exchange(toMove.element_, 0)) {} + ~Zp_field_element() = default; + /** * @brief operator+= */ - friend void operator+=(Zp_field_element& f1, const Zp_field_element& f2) { + friend void operator+=(Zp_field_element& f1, const Zp_field_element& f2) + { f1.element_ = Zp_field_element::_add(f1.element_, f2.element_); } + /** * @brief operator+ */ - friend Zp_field_element operator+(Zp_field_element f1, const Zp_field_element& f2) { + friend Zp_field_element operator+(Zp_field_element f1, const Zp_field_element& f2) + { f1 += f2; return f1; } + /** * @brief operator+= */ template > - friend void operator+=(Zp_field_element& f, const Integer_type& v) { + friend void operator+=(Zp_field_element& f, const Integer_type& v) + { f.element_ = Zp_field_element::_add(f.element_, _get_value(v)); } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Zp_field_element operator+(Zp_field_element f, const Integer_type& v) { + friend Zp_field_element operator+(Zp_field_element f, const Integer_type& v) + { f += v; return f; } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator+(const Integer_type& v, Zp_field_element f) { + friend Integer_type operator+(const Integer_type& v, Zp_field_element f) + { f += v; return f.element_; } @@ -114,80 +131,98 @@ class Zp_field_element { /** * @brief operator-= */ - friend void operator-=(Zp_field_element& f1, const Zp_field_element& f2) { + friend void operator-=(Zp_field_element& f1, const Zp_field_element& f2) + { f1.element_ = Zp_field_element::_subtract(f1.element_, f2.element_); } + /** * @brief operator- */ - friend Zp_field_element operator-(Zp_field_element f1, const Zp_field_element& f2) { + friend Zp_field_element operator-(Zp_field_element f1, const Zp_field_element& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= */ template > - friend void operator-=(Zp_field_element& f, const Integer_type& v) { + friend void operator-=(Zp_field_element& f, const Integer_type& v) + { f.element_ = Zp_field_element::_subtract(f.element_, _get_value(v)); } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Zp_field_element operator-(Zp_field_element f, const Integer_type& v) { + friend Zp_field_element operator-(Zp_field_element f, const Integer_type& v) + { f -= v; return f; } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator-(const Integer_type& v, const Zp_field_element& f) { + friend Integer_type operator-(const Integer_type& v, const Zp_field_element& f) + { return Zp_field_element::_subtract(_get_value(v), f.element_); } /** * @brief operator*= */ - friend void operator*=(Zp_field_element& f1, const Zp_field_element& f2) { + friend void operator*=(Zp_field_element& f1, const Zp_field_element& f2) + { f1.element_ = Zp_field_element::_multiply(f1.element_, f2.element_); } + /** * @brief operator* */ - friend Zp_field_element operator*(Zp_field_element f1, const Zp_field_element& f2) { + friend Zp_field_element operator*(Zp_field_element f1, const Zp_field_element& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= */ template > - friend void operator*=(Zp_field_element& f, const Integer_type& v) { + friend void operator*=(Zp_field_element& f, const Integer_type& v) + { f.element_ = Zp_field_element::_multiply(f.element_, _get_value(v)); } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Zp_field_element operator*(Zp_field_element f, const Integer_type& v) { + friend Zp_field_element operator*(Zp_field_element f, const Integer_type& v) + { f *= v; return f; } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator*(const Integer_type& v, Zp_field_element f) { + friend Integer_type operator*(const Integer_type& v, Zp_field_element f) + { f *= v; return f.element_; } @@ -195,71 +230,91 @@ class Zp_field_element { /** * @brief operator== */ - friend bool operator==(const Zp_field_element& f1, const Zp_field_element& f2) { - return f1.element_ == f2.element_; - } + friend bool operator==(const Zp_field_element& f1, const Zp_field_element& f2) { return f1.element_ == f2.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Integer_type& v, const Zp_field_element& f) { + friend bool operator==(const Integer_type& v, const Zp_field_element& f) + { return Zp_field_element::_get_value(v) == f.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Zp_field_element& f, const Integer_type& v) { + friend bool operator==(const Zp_field_element& f, const Integer_type& v) + { return Zp_field_element::_get_value(v) == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Zp_field_element& f1, const Zp_field_element& f2) { return !(f1 == f2); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Integer_type& v, const Zp_field_element& f) { + friend bool operator!=(const Integer_type& v, const Zp_field_element& f) + { return !(v == f); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Zp_field_element& f, const Integer_type& v) { + friend bool operator!=(const Zp_field_element& f, const Integer_type& v) + { return !(v == f); } /** * @brief Assign operator. */ - Zp_field_element& operator=(Zp_field_element other) { + Zp_field_element& operator=(Zp_field_element other) + { std::swap(element_, other.element_); return *this; } + + /** + * @brief Move assign operator. + */ + Zp_field_element& operator=(Zp_field_element&& other) noexcept + { + element_ = std::exchange(other.element_, 0); + return *this; + } + /** * @brief Assign operator. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - Zp_field_element& operator=(const Integer_type& value) { + Zp_field_element& operator=(const Integer_type& value) + { element_ = _get_value(value); return *this; } + /** * @brief Swap operator. */ - friend void swap(Zp_field_element& f1, Zp_field_element& f2) { std::swap(f1.element_, f2.element_); } + friend void swap(Zp_field_element& f1, Zp_field_element& f2) noexcept { std::swap(f1.element_, f2.element_); } /** * @brief Casts the element into an unsigned int. @@ -268,57 +323,64 @@ class Zp_field_element { /** * @brief Returns the inverse of the element in the field. - * + * * @return The inverse. */ - Zp_field_element get_inverse() const { + Zp_field_element get_inverse() const + { if (element_ != 0 && inverse_[element_] == 0) { // initialize everything at instantiation instead? inverse_[element_] = _get_inverse(element_); } return Zp_field_element(inverse_[element_]); } + /** * @brief For interface purposes with multi-fields. Returns the inverse together with the argument. - * + * * @param productOfCharacteristics Some value. * @return Pair whose first element is the inverse and the second element is @p productOfCharacteristics. */ - std::pair get_partial_inverse(unsigned int productOfCharacteristics) const { + std::pair get_partial_inverse(unsigned int productOfCharacteristics) const + { return {get_inverse(), productOfCharacteristics}; } /** * @brief Returns the additive identity of the field. - * + * * @return 0. */ static Zp_field_element get_additive_identity() { return Zp_field_element(); } + /** * @brief Returns the multiplicative identity of the field. - * + * * @return 1. */ static Zp_field_element get_multiplicative_identity() { return Zp_field_element(1); } + /** * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. - * + * * @param productOfCharacteristics Some value. * @return 1. */ - static Zp_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics) { + static Zp_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics) + { return Zp_field_element(1); } + /** * @brief Returns the current characteristic. - * + * * @return The value of the current characteristic. */ static constexpr unsigned int get_characteristic() { return characteristic; } /** * @brief Returns the value of the element. - * + * * @return Value of the element. */ Element get_value() const { return element_; } @@ -326,10 +388,11 @@ class Zp_field_element { // static constexpr bool handles_only_z2() { return false; } private: - Element element_; /**< Field element. */ - static inline std::array inverse_; /**< All inverse elements. */ + Element element_; /**< Field element. */ + static inline std::array inverse_; /**< All inverse elements. */ - static Element _add(Element element, Element v) { + static Element _add(Element element, Element v) + { if (UINT_MAX - element < v) { // automatic unsigned integer overflow behaviour will make it work element += v; @@ -342,7 +405,9 @@ class Zp_field_element { return element; } - static Element _subtract(Element element, Element v) { + + static Element _subtract(Element element, Element v) + { if (element < v) { element += characteristic; } @@ -350,7 +415,9 @@ class Zp_field_element { return element; } - static Element _multiply(Element element, Element v) { + + static Element _multiply(Element element, Element v) + { Element a = element; element = 0; Element temp_b; @@ -369,7 +436,9 @@ class Zp_field_element { return element; } - static int _get_inverse(Element element) { + + static int _get_inverse(Element element) + { // to solve: Ax + My = 1 int M = characteristic; int A = element; @@ -392,7 +461,8 @@ class Zp_field_element { } template > - static constexpr Element _get_value(Integer_type e) { + static constexpr Element _get_value(Integer_type e) + { if constexpr (std::is_signed_v) { if (e < -static_cast(characteristic)) e = e % characteristic; if (e < 0) return e += characteristic; @@ -402,7 +472,8 @@ class Zp_field_element { } } - static constexpr bool _is_prime() { + static constexpr bool _is_prime() + { if (characteristic <= 1) return false; if (characteristic <= 3) return true; if (characteristic % 2 == 0 || characteristic % 3 == 0) return false; diff --git a/multipers/gudhi/gudhi/Fields/Zp_field_operators.h b/multipers/gudhi/gudhi/Fields/Zp_field_operators.h index 3ebe6f3b..8e1b7a2d 100644 --- a/multipers/gudhi/gudhi/Fields/Zp_field_operators.h +++ b/multipers/gudhi/gudhi/Fields/Zp_field_operators.h @@ -41,42 +41,34 @@ class Zp_field_operators { public: using Element = Unsigned_integer_type; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Characteristic = Element; /**< Type for the field characteristic. */ template using isSignedInteger = std::enable_if_t >; + /** + * @brief Value of a non initialized characteristic. + */ + constexpr static const Characteristic nullCharacteristic = 0; + /** * @brief Default constructor. If a non-zero characteristic is given, initializes the field with it. * The characteristic can later be changed again or initialized with @ref set_characteristic. * * @param characteristic Prime number corresponding to the desired characteristic of the field. */ - Zp_field_operators(Characteristic characteristic = 0) : characteristic_(0) { - if (characteristic != 0) set_characteristic(characteristic); + Zp_field_operators(Characteristic characteristic = nullCharacteristic) : characteristic_(nullCharacteristic) + { + if (characteristic != nullCharacteristic) set_characteristic(characteristic); } - /** - * @brief Copy constructor. - * - * @param toCopy Operators to copy. - */ - Zp_field_operators(const Zp_field_operators& toCopy) - : characteristic_(toCopy.characteristic_), inverse_(toCopy.inverse_) {} - /** - * @brief Move constructor. - * - * @param toMove Operators to move. - */ - Zp_field_operators(Zp_field_operators&& toMove) noexcept - : characteristic_(std::exchange(toMove.characteristic_, 0)), inverse_(std::move(toMove.inverse_)) {} /** * @brief Sets the characteristic of the field. - * + * * @param characteristic Prime number corresponding to the desired characteristic of the field. */ - void set_characteristic(Characteristic characteristic) { - if (characteristic <= 1) - throw std::invalid_argument("Characteristic must be strictly positive and a prime number."); + void set_characteristic(Characteristic characteristic) + { + if (characteristic <= 1) throw std::invalid_argument("Characteristic must be a strictly positive prime number."); inverse_.resize(characteristic); inverse_[0] = 0; @@ -93,9 +85,10 @@ class Zp_field_operators characteristic_ = characteristic; } + /** * @brief Returns the current characteristic. - * + * * @return The value of the current characteristic. */ const Characteristic& get_characteristic() const { return characteristic_; } @@ -103,21 +96,23 @@ class Zp_field_operators /** * @brief Returns the value of an integer in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @param e Unsigned integer to return the value from. * @return @p e modulo the current characteristic, such that the result is positive. */ Element get_value(Element e) const { return e < characteristic_ ? e : e % characteristic_; } + /** * @brief Returns the value of an integer in the field. * That is the positive value of the integer modulo the current characteristic. - * + * * @tparam Signed_integer_type A native signed integer type: int, long int, etc. * @param e Integer to return the value from. * @return @p e modulo the current characteristic, such that the result is positive. */ template > - Element get_value(Signed_integer_type e) const { + Element get_value(Signed_integer_type e) const + { if (e < -static_cast(characteristic_)) e = e % characteristic_; if (e < 0) return e += characteristic_; return e < static_cast(characteristic_) ? e : e % characteristic_; @@ -125,77 +120,73 @@ class Zp_field_operators /** * @brief Returns the sum of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 + e2) % characteristic`, such that the result is positive. */ - Element add(Element e1, Element e2) const { - return _add(get_value(e1), get_value(e2), characteristic_); - } + Element add(Element e1, Element e2) const { return _add(get_value(e1), get_value(e2), characteristic_); } /** * @brief Stores in the first element the sum of two given elements in the field, that is * `(e1 + e2) % characteristic`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void add_inplace(Element& e1, Element e2) const { - e1 = _add(get_value(e1), get_value(e2), characteristic_); - } + void add_inplace(Element& e1, Element e2) const { e1 = _add(get_value(e1), get_value(e2), characteristic_); } /** * @brief Returns the subtraction in the field of the first element by the second element. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 - e2) % characteristic`, such that the result is positive. */ - Element subtract(Element e1, Element e2) const { - return _subtract(get_value(e1), get_value(e2), characteristic_); - } + Element subtract(Element e1, Element e2) const { return _subtract(get_value(e1), get_value(e2), characteristic_); } /** * @brief Stores in the first element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % 2`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_front(Element& e1, Element e2) const { + void subtract_inplace_front(Element& e1, Element e2) const + { e1 = _subtract(get_value(e1), get_value(e2), characteristic_); } + /** * @brief Stores in the second element the subtraction in the field of the first element by the second element, * that is `(e1 - e2) % 2`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void subtract_inplace_back(Element e1, Element& e2) const { + void subtract_inplace_back(Element e1, Element& e2) const + { e2 = _subtract(get_value(e1), get_value(e2), characteristic_); } /** * @brief Returns the multiplication of two elements in the field. - * + * * @param e1 First element. * @param e2 Second element. * @return `(e1 * e2) % characteristic`, such that the result is positive. */ - Element multiply(Element e1, Element e2) const { - return _multiply(get_value(e1), get_value(e2), characteristic_); - } + Element multiply(Element e1, Element e2) const { return _multiply(get_value(e1), get_value(e2), characteristic_); } /** * @brief Stores in the first element the multiplication of two given elements in the field, * that is `(e1 * e2) % characteristic`, such that the result is positive. - * + * * @param e1 First element. * @param e2 Second element. */ - void multiply_inplace(Element& e1, Element e2) const { + void multiply_inplace(Element& e1, Element e2) const + { e1 = _multiply(get_value(e1), get_value(e2), characteristic_); } @@ -203,47 +194,44 @@ class Zp_field_operators * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. * @return `(e * m + a) % characteristic`, such that the result is positive. */ - Element multiply_and_add(Element e, Element m, Element a) const { return get_value(e * m + a); } + Element multiply_and_add(Element e, Element m, Element a) const { return get_value((e * m) + a); } /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the first element. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_front(Element& e, Element m, Element a) const { - e = get_value(e * m + a); - } + void multiply_and_add_inplace_front(Element& e, Element m, Element a) const { e = get_value((e * m) + a); } + /** * @brief Multiplies the first element with the second one and adds the third one, that is * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the third element. * * @warning Not overflow safe. - * + * * @param e First element. * @param m Second element. * @param a Third element. */ - void multiply_and_add_inplace_back(Element e, Element m, Element& a) const { - a = get_value(e * m + a); - } + void multiply_and_add_inplace_back(Element e, Element m, Element& a) const { a = get_value((e * m) + a); } /** * @brief Adds the first element to the second one and multiplies the third one with it. * Returns the result in the field. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. @@ -256,31 +244,28 @@ class Zp_field_operators * `((e + a) * m) % characteristic`, such that the result is positive. Stores the result in the first element. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_front(Element& e, Element a, Element m) const { - e = get_value((e + a) * m); - } + void add_and_multiply_inplace_front(Element& e, Element a, Element m) const { e = get_value((e + a) * m); } + /** * @brief Adds the first element to the second one and multiplies the third one with it, that is * `((e + a) * m) % characteristic`, such that the result is positive. Stores the result in the third element. * * @warning Not overflow safe. - * + * * @param e First element. * @param a Second element. * @param m Third element. */ - void add_and_multiply_inplace_back(Element e, Element a, Element& m) const { - m = get_value((e + a) * m); - } + void add_and_multiply_inplace_back(Element e, Element a, Element& m) const { m = get_value((e + a) * m); } /** * @brief Returns true if the two given elements are equal in the field, false otherwise. - * + * * @param e1 First element to compare. * @param e2 Second element to compare. * @return true If `e1 % characteristic == e2 % characteristic`. @@ -290,69 +275,64 @@ class Zp_field_operators /** * @brief Returns the inverse of the given element in the field. - * + * * @param e Element to get the inverse from. * @return Inverse in the current field of `e % characteristic`. */ Element get_inverse(Element e) const { return inverse_[get_value(e)]; } + /** * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. - * + * * @param e Element to get the inverse from. * @param productOfCharacteristics Some value. * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. */ - std::pair get_partial_inverse(Element e, - Characteristic productOfCharacteristics) const { + std::pair get_partial_inverse(Element e, Characteristic productOfCharacteristics) const + { return {get_inverse(e), productOfCharacteristics}; } /** * @brief Returns the additive identity of the field. - * + * * @return 0. */ static constexpr Element get_additive_identity() { return 0; } + /** * @brief Returns the multiplicative identity of the field. - * + * * @return 1. */ static constexpr Element get_multiplicative_identity() { return 1; } + /** * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. - * + * * @param productOfCharacteristics Some value. * @return 1. */ - static constexpr Element get_partial_multiplicative_identity( - [[maybe_unused]] Characteristic productOfCharacteristics) { + static constexpr Element get_partial_multiplicative_identity([[maybe_unused]] Characteristic productOfCharacteristics) + { return 1; } - // static constexpr bool handles_only_z2() { return false; } - - /** - * @brief Assign operator. - */ - Zp_field_operators& operator=(Zp_field_operators other) { - std::swap(characteristic_, other.characteristic_); - inverse_.swap(other.inverse_); - return *this; - } /** * @brief Swap operator. */ - friend void swap(Zp_field_operators& f1, Zp_field_operators& f2) { + friend void swap(Zp_field_operators& f1, Zp_field_operators& f2) noexcept + { std::swap(f1.characteristic_, f2.characteristic_); f1.inverse_.swap(f2.inverse_); } private: - Characteristic characteristic_; /**< Current characteristic of the field. */ - std::vector inverse_; /**< All inverse elements. */ + Characteristic characteristic_; /**< Current characteristic of the field. */ + std::vector inverse_; /**< All inverse elements. */ - static Element _add(Element e1, Element e2, Characteristic characteristic) { + static Element _add(Element e1, Element e2, Characteristic characteristic) + { if (UINT_MAX - e1 < e2) { // automatic unsigned integer overflow behaviour will make it work e1 += e2; @@ -365,7 +345,9 @@ class Zp_field_operators return e1; } - static Element _subtract(Element e1, Element e2, Characteristic characteristic) { + + static Element _subtract(Element e1, Element e2, Characteristic characteristic) + { if (e1 < e2) { e1 += characteristic; } @@ -373,7 +355,9 @@ class Zp_field_operators return e1; } - static Element _multiply(Element e1, Element e2, Characteristic characteristic) { + + static Element _multiply(Element e1, Element e2, Characteristic characteristic) + { unsigned int a = e1; e1 = 0; unsigned int temp_b; diff --git a/multipers/gudhi/gudhi/Fields/Zp_field_shared.h b/multipers/gudhi/gudhi/Fields/Zp_field_shared.h index fa304d93..09143489 100644 --- a/multipers/gudhi/gudhi/Fields/Zp_field_shared.h +++ b/multipers/gudhi/gudhi/Fields/Zp_field_shared.h @@ -19,7 +19,7 @@ #include #include -#include +#include #include namespace Gudhi { @@ -39,10 +39,11 @@ namespace persistence_fields { */ template > > -class Shared_Zp_field_element { +class Shared_Zp_field_element +{ public: using Element = Unsigned_integer_type; /**< Type for the elements in the field. */ - using Characteristic = Element; /**< Type for the field characteristic. */ + using Characteristic = Element; /**< Type for the field characteristic. */ template using isInteger = std::enable_if_t >; @@ -50,34 +51,41 @@ class Shared_Zp_field_element { * @brief Default constructor. Sets the element to 0. */ Shared_Zp_field_element() : element_(0) {} + /** * @brief Constructor setting the element to the given value. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. * @param element Value of the element. */ template > - Shared_Zp_field_element(Integer_type element) : element_(_get_value(element)) {} + Shared_Zp_field_element(Integer_type element) : element_(_get_value(element)) + {} + /** * @brief Copy constructor. - * + * * @param toCopy Element to copy. */ - Shared_Zp_field_element(const Shared_Zp_field_element& toCopy) : element_(toCopy.element_) {} + Shared_Zp_field_element(const Shared_Zp_field_element& toCopy) = default; + /** * @brief Move constructor. - * + * * @param toMove Element to move. */ Shared_Zp_field_element(Shared_Zp_field_element&& toMove) noexcept : element_(std::exchange(toMove.element_, 0)) {} + ~Shared_Zp_field_element() = default; + /** * @brief Initialize the field to the given characteristic. * Should be called first before constructing the field elements. - * + * * @param characteristic Characteristic of the field. A positive prime number. */ - static void initialize(Characteristic characteristic) { + static void initialize(Characteristic characteristic) + { if (characteristic <= 1) throw std::invalid_argument("Characteristic must be strictly positive and a prime number."); @@ -99,7 +107,7 @@ class Shared_Zp_field_element { /** * @brief Returns the value of the element. - * + * * @return Value of the element. */ Element get_value() const { return element_; } @@ -107,42 +115,51 @@ class Shared_Zp_field_element { /** * @brief operator+= */ - friend void operator+=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + friend void operator+=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) + { f1.element_ = Shared_Zp_field_element::_add(f1.element_, f2.element_); } + /** * @brief operator+ */ - friend Shared_Zp_field_element operator+(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + friend Shared_Zp_field_element operator+(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) + { f1 += f2; return f1; } + /** * @brief operator+= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator+=(Shared_Zp_field_element& f, const Integer_type& v) { + friend void operator+=(Shared_Zp_field_element& f, const Integer_type& v) + { f.element_ = Shared_Zp_field_element::_add(f.element_, _get_value(v)); } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Shared_Zp_field_element operator+(Shared_Zp_field_element f, const Integer_type& v) { + friend Shared_Zp_field_element operator+(Shared_Zp_field_element f, const Integer_type& v) + { f += v; return f; } + /** * @brief operator+ - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator+(const Integer_type& v, Shared_Zp_field_element f) { + friend Integer_type operator+(const Integer_type& v, Shared_Zp_field_element f) + { f += v; return f.element_; } @@ -150,84 +167,102 @@ class Shared_Zp_field_element { /** * @brief operator-= */ - friend void operator-=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + friend void operator-=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) + { f1.element_ = Shared_Zp_field_element::_subtract(f1.element_, f2.element_); } + /** * @brief operator- */ - friend Shared_Zp_field_element operator-(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + friend Shared_Zp_field_element operator-(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) + { f1 -= f2; return f1; } + /** * @brief operator-= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator-=(Shared_Zp_field_element& f, const Integer_type& v) { + friend void operator-=(Shared_Zp_field_element& f, const Integer_type& v) + { f.element_ = Shared_Zp_field_element::_subtract(f.element_, _get_value(v)); } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Shared_Zp_field_element operator-(Shared_Zp_field_element f, const Integer_type& v) { + friend Shared_Zp_field_element operator-(Shared_Zp_field_element f, const Integer_type& v) + { f -= v; return f; } + /** * @brief operator- - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Integer_type operator-(const Integer_type& v, const Shared_Zp_field_element& f) { + friend Integer_type operator-(const Integer_type& v, const Shared_Zp_field_element& f) + { return Shared_Zp_field_element::_subtract(_get_value(v), f.element_); } /** * @brief operator*= */ - friend void operator*=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + friend void operator*=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) + { f1.element_ = Shared_Zp_field_element::_multiply(f1.element_, f2.element_); } + /** * @brief operator* */ - friend Shared_Zp_field_element operator*(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + friend Shared_Zp_field_element operator*(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) + { f1 *= f2; return f1; } + /** * @brief operator*= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend void operator*=(Shared_Zp_field_element& f, const Integer_type& v) { + friend void operator*=(Shared_Zp_field_element& f, const Integer_type& v) + { f.element_ = Shared_Zp_field_element::_multiply(f.element_, _get_value(v)); } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend Shared_Zp_field_element operator*(Shared_Zp_field_element f, const Integer_type& v) { + friend Shared_Zp_field_element operator*(Shared_Zp_field_element f, const Integer_type& v) + { f *= v; return f; } + /** * @brief operator* - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic. if signed. */ template > - friend Integer_type operator*(const Integer_type& v, Shared_Zp_field_element f) { + friend Integer_type operator*(const Integer_type& v, Shared_Zp_field_element f) + { f *= v; return f.element_; } @@ -235,71 +270,97 @@ class Shared_Zp_field_element { /** * @brief operator== */ - friend bool operator==(const Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + friend bool operator==(const Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) + { return f1.element_ == f2.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Integer_type& v, const Shared_Zp_field_element& f) { + friend bool operator==(const Integer_type& v, const Shared_Zp_field_element& f) + { return Shared_Zp_field_element::_get_value(v) == f.element_; } + /** * @brief operator== - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator==(const Shared_Zp_field_element& f, const Integer_type& v) { + friend bool operator==(const Shared_Zp_field_element& f, const Integer_type& v) + { return Shared_Zp_field_element::_get_value(v) == f.element_; } + /** * @brief operator!= */ friend bool operator!=(const Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { return !(f1 == f2); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Integer_type& v, const Shared_Zp_field_element& f) { + friend bool operator!=(const Integer_type& v, const Shared_Zp_field_element& f) + { return !(v == f); } + /** * @brief operator!= - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - friend bool operator!=(const Shared_Zp_field_element& f, const Integer_type& v) { + friend bool operator!=(const Shared_Zp_field_element& f, const Integer_type& v) + { return !(v == f); } /** * @brief Assign operator. */ - Shared_Zp_field_element& operator=(Shared_Zp_field_element other) { + Shared_Zp_field_element& operator=(Shared_Zp_field_element other) + { std::swap(element_, other.element_); return *this; } + + /** + * @brief Move assign operator. + */ + Shared_Zp_field_element& operator=(Shared_Zp_field_element&& other) noexcept + { + element_ = std::exchange(other.element_, 0); + return *this; + } + /** * @brief Assign operator. - * + * * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. */ template > - Shared_Zp_field_element& operator=(const Integer_type& value) { + Shared_Zp_field_element& operator=(const Integer_type& value) + { element_ = Shared_Zp_field_element::_get_value(value); return *this; } + /** * @brief Swap operator. */ - friend void swap(Shared_Zp_field_element& f1, Shared_Zp_field_element& f2) { std::swap(f1.element_, f2.element_); } + friend void swap(Shared_Zp_field_element& f1, Shared_Zp_field_element& f2) noexcept + { + std::swap(f1.element_, f2.element_); + } /** * @brief Casts the element into an unsigned int. @@ -308,46 +369,51 @@ class Shared_Zp_field_element { /** * @brief Returns the inverse of the element in the field. - * + * * @return The inverse. */ Shared_Zp_field_element get_inverse() const { return Shared_Zp_field_element(inverse_[element_]); } + /** * @brief For interface purposes with multi-fields. Returns the inverse together with the argument. - * + * * @param productOfCharacteristics Some value. * @return Pair whose first element is the inverse and the second element is @p productOfCharacteristics. */ - std::pair get_partial_inverse( - Characteristic productOfCharacteristics) const { + std::pair get_partial_inverse(Characteristic productOfCharacteristics) const + { return {get_inverse(), productOfCharacteristics}; } /** * @brief Returns the additive identity of the field. - * + * * @return 0. */ static Shared_Zp_field_element get_additive_identity() { return Shared_Zp_field_element(); } + /** * @brief Returns the multiplicative identity of the field. - * + * * @return 1. */ static Shared_Zp_field_element get_multiplicative_identity() { return Shared_Zp_field_element(1); } + /** * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. - * + * * @param productOfCharacteristics Some value. * @return 1. */ static Shared_Zp_field_element get_partial_multiplicative_identity( - [[maybe_unused]] Characteristic productOfCharacteristics) { + [[maybe_unused]] Characteristic productOfCharacteristics) + { return Shared_Zp_field_element(1); } + /** * @brief Returns the current characteristic. - * + * * @return The value of the current characteristic. */ static Characteristic get_characteristic() { return characteristic_; } @@ -355,11 +421,12 @@ class Shared_Zp_field_element { // static constexpr bool handles_only_z2() { return false; } private: - Element element_; /**< Field element. */ - static inline Characteristic characteristic_; /**< Current characteristic of the field. */ - static inline std::vector inverse_; /**< All inverse elements. */ + Element element_; /**< Field element. */ + static inline Characteristic characteristic_; /**< Current characteristic of the field. */ + static inline std::vector inverse_; /**< All inverse elements. */ - static Element _add(Element element, Element v) { + static Element _add(Element element, Element v) + { if (UINT_MAX - element < v) { // automatic unsigned integer overflow behaviour will make it work element += v; @@ -372,7 +439,9 @@ class Shared_Zp_field_element { return element; } - static Element _subtract(Element element, Element v) { + + static Element _subtract(Element element, Element v) + { if (element < v) { element += characteristic_; } @@ -380,7 +449,9 @@ class Shared_Zp_field_element { return element; } - static Element _multiply(Element element, Element v) { + + static Element _multiply(Element element, Element v) + { Element a = element; element = 0; Element temp_b; @@ -401,8 +472,9 @@ class Shared_Zp_field_element { } template > - static constexpr Element _get_value(Integer_type e) { - if constexpr (std::is_signed_v){ + static constexpr Element _get_value(Integer_type e) + { + if constexpr (std::is_signed_v) { if (e < -static_cast(characteristic_)) e = e % characteristic_; if (e < 0) return e += characteristic_; return e < static_cast(characteristic_) ? e : e % characteristic_; diff --git a/multipers/gudhi/gudhi/Matrix.h b/multipers/gudhi/gudhi/Matrix.h index 67fcec5c..87f6a17f 100644 --- a/multipers/gudhi/gudhi/Matrix.h +++ b/multipers/gudhi/gudhi/Matrix.h @@ -8,7 +8,8 @@ * - YYYY/MM Author: Description of the modification */ -/** @file Matrix.h +/** + * @file Matrix.h * @author Hannah Schreiber * @brief Contains @ref Gudhi::persistence_matrix::Matrix class. */ @@ -64,7 +65,6 @@ #include #include #include -#include #include /// Gudhi namespace. @@ -84,7 +84,7 @@ namespace persistence_matrix { * The are roughly three types of matrices available and one is selected automatically depending on the options chosen: * - @anchor basematrix a @ref basematrix "basic matrix" which can represent any matrix and therefore will not make any * assumption on its content. It is the only matrix type with the option of column compression (as it is the only one - * where it makes sense). This type is choosen by default when none of the homology related options are set to true: + * where it makes sense). This type is chosen by default when none of the homology related options are set to true: * @ref PersistenceMatrixOptions::has_column_pairings, @ref PersistenceMatrixOptions::has_vine_update and * @ref PersistenceMatrixOptions::can_retrieve_representative_cycles. * - @anchor boundarymatrix a @ref boundarymatrix "boundary matrix" @f$ B = R \cdot U @f$ which either stores only @@ -125,7 +125,7 @@ namespace persistence_matrix { * the boundaries. If at the insertion of @f$ c @f$, its ID was not specified and it was the @f$ n^{th} @f$ insertion, * it is assumed that the ID is @f$ n @f$ (which means that @ref IDIdx and @ref PosIdx will only start to differ when * swaps or removals are performed). If an ID is specified at the insertion of @f$ c @f$, the ID is stored as the - * @ref IDIdx of @f$ c @f$. IDs can be freely choosen with the only restriction that they have to be strictly + * @ref IDIdx of @f$ c @f$. IDs can be freely chosen with the only restriction that they have to be strictly * increasing in the order of the filtration at initialisation. * * In conclusion, with default values, if no vine swaps or removals occurs, all three indexing schemes are the same. @@ -142,9 +142,10 @@ namespace persistence_matrix { * See description of @ref PersistenceMatrixOptions for more details. */ template > -class Matrix { +class Matrix +{ public: - using Option_list = PersistenceMatrixOptions; //to make it accessible from the other classes + using Option_list = PersistenceMatrixOptions; // to make it accessible from the other classes using Index = typename PersistenceMatrixOptions::Index; /**< Type of @ref MatIdx index. */ using ID_index = typename PersistenceMatrixOptions::Index; /**< Type of @ref IDIdx index. */ using Pos_index = typename PersistenceMatrixOptions::Index; /**< Type of @ref PosIdx index. */ @@ -153,23 +154,30 @@ class Matrix { /** * @brief coefficients field type. */ - using Field_operators = - typename std::conditional::type; + using Field_operators = std::conditional_t; /** * @brief Type of a field element. */ using Element = typename Field_operators::Element; using Characteristic = typename Field_operators::Characteristic; - + + /** + * @brief Returns value from a type when not set. + */ + template + static constexpr T get_null_value() + { + return -1; + } + /** * @brief Type for a bar in the computed barcode. Stores the birth, death and dimension of the bar. */ using Bar = Persistence_interval; - //tags for boost to associate a row and a column to a same entry + // tags for boost to associate a row and a column to a same entry struct Matrix_row_tag; struct Matrix_column_tag; @@ -183,37 +191,30 @@ class Matrix { boost::intrusive::set_base_hook, boost::intrusive::link_mode >; - //Two dummies are necessary to avoid double inheritance as an entry can inherit both a row and a column hook. + // Two dummies are necessary to avoid double inheritance as an entry can inherit both a row and a column hook. struct Dummy_row_hook {}; + struct Dummy_column_hook {}; - using Row_hook = typename std::conditional::type; - using Column_hook = typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::INTRUSIVE_LIST, - Base_hook_matrix_list_column, - typename std::conditional::type - >::type; - - //Option to store the column index within the entry (additionally to the row index). Necessary only with row access. - using Entry_column_index_option = - typename std::conditional, - Dummy_entry_column_index_mixin - >::type; - //Option to store the value of the entry. - //Unnecessary for values in Z_2 as there are always 1 (0-valued entries are never stored). - using Entry_field_element_option = - typename std::conditional - >::type; + using Row_hook = + std::conditional_t; + using Column_hook = + std::conditional_t >; + + // Option to store the column index within the entry (additionally to the row index). Necessary only with row access. + using Entry_column_index_option = std::conditional_t, + Dummy_entry_column_index_mixin>; + // Option to store the value of the entry. + // Unnecessary for values in Z_2 as there are always 1 (0-valued entries are never stored). + using Entry_field_element_option = std:: + conditional_t >; /** * @brief Type of a matrix entry. See @ref Entry for a more detailed description. */ @@ -238,16 +239,15 @@ class Matrix { * whose first element is the row index of the entry and the second element is the value of the entry (which again is * assumed to be non-zero). The column index of the row is always deduced from the context in which the type is used. */ - using Entry_representative = typename std::conditional - >::type; + using Entry_representative = + std::conditional_t >; /** * @brief Compares two entries by their position in the row. They are assume to be in the same row. */ struct RowEntryComp { - bool operator()(const Matrix_entry& c1, const Matrix_entry& c2) const { + bool operator()(const Matrix_entry& c1, const Matrix_entry& c2) const + { return c1.get_column_index() < c2.get_column_index(); } }; @@ -257,65 +257,46 @@ class Matrix { * @ref PersistenceMatrixOptions::has_intrusive_rows is true, or a set of @ref Matrix_entry (ordered by * column index) otherwise. */ - using Row = - typename std::conditional, - boost::intrusive::base_hook - >, - std::set - >::type; + using Row = std::conditional_t, + boost::intrusive::base_hook >, + std::set >; using Row_container = - typename std::conditional, - std::vector - >::type; - - //Row access at column level - using Row_access_option = - typename std::conditional >, - Dummy_row_access - >::type; - //Row access at matrix level + std::conditional_t, std::vector >; + + // Row access at column level + using Row_access_option = std::conditional_t >, + Dummy_row_access>; + // Row access at matrix level using Matrix_row_access_option = - typename std::conditional, - Dummy_matrix_row_access - >::type; + std::conditional_t, + Dummy_matrix_row_access>; template - using Dictionary = - typename std::conditional, - std::vector - >::type; + using Dictionary = std::conditional_t, + std::vector >; static const bool isNonBasic = PersistenceMatrixOptions::has_column_pairings || PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles; - using Column_dimension_option = - typename std::conditional >, - Dummy_dimension_holder - >::type; - //Extra information needed for a column when the matrix is a @ref chainmatrix "chain matrix". - using Chain_column_option = - typename std::conditional >, - Dummy_chain_properties - >::type; + using Column_dimension_option = std:: + conditional_t >, Dummy_dimension_holder>; + // Extra information needed for a column when the matrix is a @ref chainmatrix "chain matrix". + using Chain_column_option = std::conditional_t >, + Dummy_chain_properties>; using Matrix_heap_column = Heap_column >; using Matrix_list_column = List_column >; using Matrix_vector_column = Vector_column >; - using Matrix_naive_vector_column = Naive_vector_column >; - using Matrix_small_vector_column = Small_vector_column >; + using Matrix_naive_vector_column = Naive_std_vector_column >; + using Matrix_small_vector_column = Naive_small_vector_column >; using Matrix_set_column = Set_column >; using Matrix_unordered_set_column = Unordered_set_column >; using Matrix_intrusive_list_column = Intrusive_list_column >; @@ -326,59 +307,64 @@ class Matrix { * @ref PersistenceMatrixOptions::column_type defined in the given options. See @ref Column_types for a more detailed * description. All columns follow the @ref PersistenceMatrixColumn concept. */ - using Column = typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::HEAP, - Matrix_heap_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::LIST, - Matrix_list_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::SET, - Matrix_set_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::UNORDERED_SET, - Matrix_unordered_set_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::VECTOR, - Matrix_vector_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::INTRUSIVE_LIST, - Matrix_intrusive_list_column, - typename std::conditional< - PersistenceMatrixOptions::column_type == Column_types::NAIVE_VECTOR, - Matrix_naive_vector_column, - typename std::conditional_t< - PersistenceMatrixOptions::column_type == Column_types::SMALL_VECTOR, - Matrix_small_vector_column, - Matrix_intrusive_set_column> - >::type - >::type - >::type - >::type - >::type - >::type - >::type; - - struct Column_z2_settings{ + using Column = std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::HEAP, + Matrix_heap_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::LIST, + Matrix_list_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::SET, + Matrix_set_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::UNORDERED_SET, + Matrix_unordered_set_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::VECTOR, + Matrix_vector_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::INTRUSIVE_LIST, + Matrix_intrusive_list_column, + std::conditional_t< + PersistenceMatrixOptions::column_type == Column_types::NAIVE_VECTOR, + Matrix_naive_vector_column, + std::conditional_t + > > > > > > >; + + struct Column_z2_settings { Column_z2_settings() : entryConstructor() {} Column_z2_settings([[maybe_unused]] Characteristic characteristic) : entryConstructor() {} - Column_z2_settings(const Column_z2_settings& toCopy) : entryConstructor() {} + Column_z2_settings([[maybe_unused]] const Column_z2_settings& toCopy) : entryConstructor() {} + Column_z2_settings([[maybe_unused]] Column_z2_settings&& toMove) noexcept : entryConstructor() {} + ~Column_z2_settings() = default; + Column_z2_settings& operator=([[maybe_unused]] const Column_z2_settings& other) { return *this; } + Column_z2_settings& operator=([[maybe_unused]] Column_z2_settings&& other) noexcept { return *this; } - Entry_constructor entryConstructor; //will be replaced by more specific allocators depending on the column type. + Entry_constructor entryConstructor; // will be replaced by more specific allocators depending on the column type. }; struct Column_zp_settings { Column_zp_settings() : operators(), entryConstructor() {} - //purposely triggers operators() instead of operators(characteristic) as the "dummy" values for the different - //operators can be different from -1. - Column_zp_settings(Characteristic characteristic) : operators(), entryConstructor() { - if (characteristic != static_cast(-1)) operators.set_characteristic(characteristic); + Column_zp_settings(Characteristic characteristic) : operators(characteristic), entryConstructor() {} + Column_zp_settings(const Column_zp_settings& toCopy) : operators(toCopy.operators), entryConstructor() {} + Column_zp_settings(Column_zp_settings&& toMove) noexcept + : operators(std::move(toMove.operators)), entryConstructor() {} + ~Column_zp_settings() = default; + Column_zp_settings& operator=(const Column_zp_settings& other) + { + operators = other.operators; + return *this; + } + Column_zp_settings& operator=(Column_zp_settings&& other) noexcept + { + operators = std::move(other.operators); + return *this; } - Column_zp_settings(const Column_zp_settings& toCopy) - : operators(toCopy.operators.get_characteristic()), entryConstructor() {} Field_operators operators; - Entry_constructor entryConstructor; //will be replaced by more specific allocators depending on the column type. + Entry_constructor entryConstructor; // will be replaced by more specific allocators depending on the column type. }; // struct Column_z2_with_rows_settings { @@ -400,161 +386,127 @@ class Matrix { // Row_container* rows; // }; - //To prepare a more flexible use of the column types later (custom allocators depending on the column type etc.) - using Column_settings = typename std::conditional< - PersistenceMatrixOptions::is_z2, - Column_z2_settings, - Column_zp_settings - >::type; + // To prepare a more flexible use of the column types later (custom allocators depending on the column type etc.) + using Column_settings = std::conditional_t; // using Column_settings = typename std::conditional< // PersistenceMatrixOptions::is_z2, - // typename std::conditional::type, - // typename std::conditional::type // >::type; - using Column_container = - typename std::conditional, - std::vector - >::type; + using Column_container = std::conditional_t, + std::vector >; static const bool hasFixedBarcode = Option_list::is_of_boundary_type && !PersistenceMatrixOptions::has_vine_update; /** * @brief Type of the computed barcode. It is either a list of @ref Matrix::Bar or a vector of @ref Matrix::Bar, * depending if bars need to be removed from the container as some point or not. */ - using Barcode = typename std::conditional, - typename std::conditional, - std::vector - >::type - >::type; - using Bar_dictionary = - typename std::conditional, //RU - std::unordered_map //boundary - >::type, - typename std::conditional, - std::vector - >::type - >::type; - - //default type for boundaries to permit list initialization directly in function parameters - using Boundary = typename std::conditional, - std::initializer_list > - >::type; - - //i.e. is simple @ref boundarymatrix "boundary matrix". Also, only needed because of the reduction algorithm. - //TODO: remove the necessity and recalculate when needed or keep it like that? + using Barcode = std::conditional_t< + hasFixedBarcode, + std::vector, + std::conditional_t, std::vector > >; + using Bar_dictionary = + std::conditional_t, // RU + std::unordered_map // boundary + >, + std::conditional_t, + std::vector > >; + + // default type for boundaries to permit list initialization directly in function parameters + using Boundary = std::conditional_t, + std::initializer_list > >; + + // i.e. is simple @ref boundarymatrix "boundary matrix". Also, only needed because of the reduction algorithm. + // TODO: remove the necessity and recalculate when needed or keep it like that? static const bool maxDimensionIsNeeded = PersistenceMatrixOptions::has_column_pairings && PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_vine_update && !PersistenceMatrixOptions::can_retrieve_representative_cycles; - using Matrix_dimension_option = typename std::conditional< - PersistenceMatrixOptions::has_matrix_maximal_dimension_access || maxDimensionIsNeeded, - typename std::conditional, - Matrix_max_dimension_holder - >::type, - Dummy_matrix_dimension_holder - >::type; - - using Master_base_matrix = - typename std::conditional >, - Base_matrix > - >::type; + using Matrix_dimension_option = + std::conditional_t, + Matrix_max_dimension_holder >, + Dummy_matrix_dimension_holder>; + + using Master_base_matrix = std::conditional_t >, + Base_matrix > >; using Master_boundary_matrix = Boundary_matrix >; using Master_RU_matrix = RU_matrix >; using Master_chain_matrix = Chain_matrix >; template - using Base_swap_option = - typename std::conditional, Base>, - Dummy_base_swap - >::type; + using Base_swap_option = std::conditional_t, Base>, + Dummy_base_swap>; using Base_pairing_option = - typename std::conditional >, - Dummy_base_pairing - >::type; - - using RU_pairing_option = - typename std::conditional >, - Dummy_ru_pairing - >::type; - using RU_vine_swap_option = - typename std::conditional >, - Dummy_ru_vine_swap - >::type; + std::conditional_t >, + Dummy_base_pairing>; + + using RU_pairing_option = std::conditional_t >, + RU_pairing > >, + Dummy_ru_pairing>; + using RU_vine_swap_option = std::conditional_t >, + Dummy_ru_vine_swap>; using RU_representative_cycles_option = - typename std::conditional >, - Dummy_ru_representative_cycles - >::type; + std::conditional_t >, + Dummy_ru_representative_cycles>; using Chain_pairing_option = - typename std::conditional >, - Dummy_chain_pairing - >::type; - using Chain_vine_swap_option = typename std::conditional >, - Dummy_chain_vine_swap - >::type; + std::conditional_t >, + Chain_pairing > >, + Dummy_chain_pairing>; + using Chain_vine_swap_option = std::conditional_t >, + Dummy_chain_vine_swap>; using Chain_representative_cycles_option = - typename std::conditional >, - Dummy_chain_representative_cycles - >::type; + std::conditional_t >, + Dummy_chain_representative_cycles>; /** * @brief Type of a representative cycle. Vector of @ref rowindex "row indices". */ - using Cycle = std::vector; //TODO: add coefficients + using Cycle = std::vector; - //Return types to factorize the corresponding methods + // Return types to factorize the corresponding methods - //The returned column is `const` if the matrix uses column compression + // The returned column is `const` if the matrix uses column compression using Returned_column = - typename std::conditional::type; - //The returned row is `const` if the matrix uses column compression + std::conditional_t; + // The returned row is `const` if the matrix uses column compression using Returned_row = - typename std::conditional::type; - //If the matrix is a chain matrix, the insertion method returns the pivots of its unpaired columns used to reduce the - //inserted boundary. Otherwise, void. + std::conditional_t; + // If the matrix is a chain matrix, the insertion method returns the pivots of its unpaired columns used to reduce the + // inserted boundary. Otherwise, void. using Insertion_return = - typename std::conditional - >::type; + std::conditional_t >; /** * @brief Default constructor. Initializes an empty matrix. @@ -597,7 +549,7 @@ class Matrix { * @ref set_characteristic before calling for the first time a method needing it. Ignored if * @ref PersistenceMatrixOptions::is_z2 is true. */ - Matrix(unsigned int numberOfColumns, Characteristic characteristic = static_cast(-1)); + Matrix(unsigned int numberOfColumns, Characteristic characteristic = Field_operators::nullCharacteristic); /** * @brief Constructs a new empty matrix with the given comparator functions. Only available when those comparators * are necessary. @@ -618,8 +570,8 @@ class Matrix { * @param deathComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first * cell is associated to a bar with strictly smaller death than the bar associated to the second one. */ - Matrix(const std::function& birthComparator, - const std::function& deathComparator); + Matrix(const std::function& birthComparator, + const std::function& deathComparator); /** * @brief Constructs a new matrix from the given ranges with the given comparator functions. * Only available when those comparators are necessary. @@ -646,9 +598,9 @@ class Matrix { * Ignored if @ref PersistenceMatrixOptions::is_z2 is true. */ template - Matrix(const std::vector& orderedBoundaries, - const std::function& birthComparator, - const std::function& deathComparator, + Matrix(const std::vector& orderedBoundaries, + const std::function& birthComparator, + const std::function& deathComparator, Characteristic characteristic = 11); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. @@ -673,29 +625,29 @@ class Matrix { * @ref set_characteristic before calling for the first time a method needing it. * Ignored if @ref PersistenceMatrixOptions::is_z2 is true. */ - Matrix(unsigned int numberOfColumns, - const std::function& birthComparator, - const std::function& deathComparator, - Characteristic characteristic = static_cast(-1)); + Matrix(unsigned int numberOfColumns, + const std::function& birthComparator, + const std::function& deathComparator, + Characteristic characteristic = Field_operators::nullCharacteristic); /** * @brief Copy constructor. - * + * * @param matrixToCopy %Matrix to copy. */ Matrix(const Matrix& matrixToCopy); /** * @brief Move constructor. * After the move, the given matrix will be empty. - * + * * @param other %Matrix to move. */ Matrix(Matrix&& other) noexcept; ~Matrix(); - //TODO: compatibility with multi fields: - // - set_characteristic(Characteristic min, Characteristic max) - // - readapt reduction? + // TODO: compatibility with multi fields: + // - set_characteristic(Characteristic min, Characteristic max) + // - readapt reduction? /** * @brief Sets the characteristic of the coefficient field if @ref PersistenceMatrixOptions::is_z2 is false, * does nothing otherwise. @@ -704,20 +656,20 @@ class Matrix { * * @warning The coefficient values stored in the matrix are stored after computing the corresponding modulo. * Therefore, changing the characteristic after is very likely to invalidate all entry values. - * + * * @param characteristic The characteristic to set. */ void set_characteristic(Characteristic characteristic); - // (TODO: if there is no row access and the column type corresponds to the internal column type of the matrix, + // (TODO: if there is no row access and the column type corresponds to the internal column type of the matrix, // moving the column instead of copying it should be possible. Is it worth implementing it?) /** * @brief Inserts a new ordered column at the end of the matrix by copying the given range of - * @ref Entry_representative. The content of the range is assumed to be sorted by increasing ID value. + * @ref Entry_representative. The content of the range is assumed to be sorted by increasing ID value. * * Only available for @ref basematrix "base matrices". * Otherwise use @ref insert_boundary which will deduce a new column from the boundary given. - * + * * @tparam Container Range of @ref Entry_representative. Assumed to have a begin(), end() and size() method. * @param column Column to be inserted. */ @@ -726,18 +678,18 @@ class Matrix { /** * @brief Inserts a new ordered column at the given index by copying the given range of @ref Entry_representative. * There should not be any other column inserted at that index which was not explicitly removed before. - * The content of the range is assumed to be sorted by increasing ID value. + * The content of the range is assumed to be sorted by increasing ID value. * * Only available for @ref basematrix "base matrices" without column compression and without row access. - * + * * @tparam Container Range of @ref Entry_representative. Assumed to have a begin(), end() and size() method. * @param column Column to be inserted. * @param columnIndex @ref MatIdx index to which the column has to be inserted. */ template void insert_column(const Container& column, Index columnIndex); - //TODO: for simple boundary matrices, add an index pointing to the first column inserted after the last call of - //get_current_barcode to enable several calls to get_current_barcode + // TODO: for simple boundary matrices, add an index pointing to the first column inserted after the last call of + // get_current_barcode to enable several calls to get_current_barcode /** * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. * This means that it is assumed that this method is called on boundaries in the order of the filtration. @@ -770,7 +722,7 @@ class Matrix { * chains used to reduce the boundary. Otherwise, nothing. */ template - Insertion_return insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + Insertion_return insert_boundary(const Boundary_range& boundary, Dimension dim = Matrix::get_null_value()); /** * @brief Only available for @ref mp_matrices "non-basic matrices". * It does the same as the other version, but allows the boundary cells to be identified without restrictions @@ -792,13 +744,15 @@ class Matrix { * chains used to reduce the boundary. Otherwise, nothing. */ template - Insertion_return insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim = -1); + Insertion_return insert_boundary(ID_index cellIndex, + const Boundary_range& boundary, + Dimension dim = Matrix::get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index. * For @ref boundarymatrix "RU matrices", is equivalent to * @ref get_column(Index columnIndex, bool inR) "get_column(columnIndex, true)". - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. * * @param columnIndex @ref MatIdx index of the column to return. * @return Reference to the column. Is `const` if the matrix has column compression. @@ -806,18 +760,18 @@ class Matrix { Returned_column& get_column(Index columnIndex); /** * @brief Only available for @ref chainmatrix "chain matrices". Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param columnIndex @ref MatIdx index of the column to return. * @return Const reference to the column. */ const Column& get_column(Index columnIndex) const; - //TODO: there is no particular reason that this method is not available for identifier indexing, - // just has to be added to the interface... + // TODO: there is no particular reason that this method is not available for identifier indexing, + // just has to be added to the interface... /** * @brief Only available for @ref boundarymatrix "RU matrices" without @ref Column_indexation_types::IDENTIFIER * indexing. Returns the column at the given @ref MatIdx index in \f$ R \f$ if @p inR is true and in \f$ U \f$ if - * @p inR is false. The type of the column depends on the choosen options, + * @p inR is false. The type of the column depends on the chosen options, * see @ref PersistenceMatrixOptions::column_type. * * @param columnIndex @ref MatIdx index of the column to return. @@ -826,12 +780,12 @@ class Matrix { */ const Column& get_column(Index columnIndex, bool inR); - //TODO: update column indices when reordering rows (after lazy swap) such that always MatIdx are returned. + // TODO: update column indices when reordering rows (after lazy swap) such that always MatIdx are returned. /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. Returns the row at the given * @ref rowindex "row index". For @ref boundarymatrix "RU matrices", is equivalent to * @ref get_row(ID_index rowIndex, bool inR) "get_row(columnIndex, true)". The type of the row depends on the - * choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. * * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" or * updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occurred. @@ -841,19 +795,19 @@ class Matrix { /** * @brief Only available for @ref chainmatrix "chain matrices" and matrices with column compression. * Returns the row at the given @ref rowindex "row index". - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. - * + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occurred. * @return Const reference to the row. */ const Row& get_row(ID_index rowIndex) const; - //TODO: there is no particular reason that this method is not available for identifier indexing, - // just has to be added to the interface... + // TODO: there is no particular reason that this method is not available for identifier indexing, + // just has to be added to the interface... /** * @brief Only available for @ref boundarymatrix "RU matrices" without @ref Column_indexation_types::IDENTIFIER * indexing. Returns the row at the given @ref rowindex "row index" in \f$ R \f$ if @p inR is true and in \f$ U \f$ if - * @p inR is false. The type of the row depends on the choosen options, see + * @p inR is false. The type of the row depends on the chosen options, see * @ref PersistenceMatrixOptions::has_intrusive_rows. * * @param rowIndex @ref rowindex "Row index" of the row to return: updated @ref IDIdx if swaps occurred. @@ -871,7 +825,7 @@ class Matrix { * @param columnIndex @ref MatIdx index of the column to remove. */ void remove_column(Index columnIndex); - //TODO: rename method to be less confusing. + // TODO: rename method to be less confusing. /** * @brief The effect varies depending on the matrices and the options: * - @ref basematrix "base matrix" and @ref boundarymatrix "boundary matrix": @@ -895,7 +849,7 @@ class Matrix { * @param rowIndex @ref rowindex "Row index" of the empty row to remove. */ void erase_empty_row(ID_index rowIndex); - //TODO: for chain matrices, replace IDIdx input with MatIdx input to homogenize. + // TODO: for chain matrices, replace IDIdx input with MatIdx input to homogenize. /** * @brief Only available for @ref boundarymatrix "RU" and @ref chainmatrix "chain matrices" and if * @ref PersistenceMatrixOptions::has_removable_columns and @ref PersistenceMatrixOptions::has_vine_update are true. @@ -915,9 +869,9 @@ class Matrix { * @ref IDIdx index. */ void remove_maximal_cell(Index columnIndex); - //TODO: See if it would be better to use something more general than a vector for columnsToSwap, such that - // the user do not have to construct the vector from scratch. Like passing iterators instead. But it would be nice, - // to still be able to do (cell, {})... + // TODO: See if it would be better to use something more general than a vector for columnsToSwap, such that + // the user do not have to construct the vector from scratch. Like passing iterators instead. But it would be nice, + // to still be able to do (cell, {})... /** * @brief Only available for @ref chainmatrix "chain matrices" and if * @ref PersistenceMatrixOptions::has_removable_columns, @ref PersistenceMatrixOptions::has_vine_update and @@ -969,13 +923,13 @@ class Matrix { Dimension get_max_dimension() const; /** * @brief Returns the current number of columns in the matrix. - * + * * @return The number of columns. */ Index get_number_of_columns() const; /** * @brief Returns the dimension of the given cell. Only available for @ref mp_matrices "non-basic matrices". - * + * * @param columnIndex @ref MatIdx index of the column representing the cell. * @return Dimension of the cell. */ @@ -989,7 +943,7 @@ class Matrix { * * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means * that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Integer_index Any signed or unsigned integer type. * @param sourceColumnIndex @ref MatIdx index of the column to add. * @param targetColumnIndex @ref MatIdx index of the target column. @@ -998,12 +952,12 @@ class Matrix { std::enable_if_t > add_to(Integer_index sourceColumnIndex, Integer_index targetColumnIndex); /** - * @brief Adds the given range of @ref Entry onto the column at @p targetColumnIndex in the matrix. Only available + * @brief Adds the given range of @ref Entry onto the column at @p targetColumnIndex in the matrix. Only available * for @ref basematrix "basic matrices". * * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which * means that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range Range of @ref Entry. Needs a begin() and end() method. A column index does not need to be * stored in the entries, even if @ref PersistenceMatrixOptions::has_row_access is true. * @param sourceColumn Source @ref Entry range. @@ -1022,7 +976,7 @@ class Matrix { * * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means * that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Integer_index Any signed or unsigned integer type. * @param sourceColumnIndex @ref MatIdx index of the column to add. * @param coefficient Value to multiply. @@ -1030,8 +984,8 @@ class Matrix { */ template std::enable_if_t > multiply_target_and_add_to(Integer_index sourceColumnIndex, - int coefficient, - Integer_index targetColumnIndex); + int coefficient, + Integer_index targetColumnIndex); /** * @brief Multiplies the target column with the coefficient and then adds the given range of @ref Entry to it. * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. Only available for @@ -1039,7 +993,7 @@ class Matrix { * * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which * means that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range Range of @ref Entry. Needs a begin() and end() method. A column index does not need to be * stored in the entries, even if @ref PersistenceMatrixOptions::has_row_access is true. * @param sourceColumn Source @ref Entry range. @@ -1048,8 +1002,8 @@ class Matrix { */ template std::enable_if_t > multiply_target_and_add_to(const Entry_range& sourceColumn, - int coefficient, - Index targetColumnIndex); + int coefficient, + Index targetColumnIndex); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. @@ -1061,7 +1015,7 @@ class Matrix { * * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means * that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Integer_index Any signed or unsigned integer type. * @param coefficient Value to multiply. * @param sourceColumnIndex @ref MatIdx index of the column to add. @@ -1069,8 +1023,8 @@ class Matrix { */ template std::enable_if_t > multiply_source_and_add_to(int coefficient, - Integer_index sourceColumnIndex, - Integer_index targetColumnIndex); + Integer_index sourceColumnIndex, + Integer_index targetColumnIndex); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. @@ -1078,7 +1032,7 @@ class Matrix { * * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which * means that all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range Range of @ref Entry. Needs a begin() and end() method. A column index does not need to be * stored in the entries, even if @ref PersistenceMatrixOptions::has_row_access is true. * @param coefficient Value to multiply. @@ -1087,8 +1041,8 @@ class Matrix { */ template std::enable_if_t > multiply_source_and_add_to(int coefficient, - const Entry_range& sourceColumn, - Index targetColumnIndex); + const Entry_range& sourceColumn, + Index targetColumnIndex); /** * @brief Zeroes the entry at the given coordinates. Not available for @ref chainmatrix "chain matrices" and for @@ -1177,7 +1131,7 @@ class Matrix { * in \f$ R \f$ if @p inR is true or in \f$ U \f$ if @p inR is false. * * Note that if @p inR is false, this method should usually return false. - * + * * @param columnIndex @ref MatIdx index of the column. * @param inR Boolean indicating in which matrix to look: if true in \f$ R \f$ and if false in \f$ U \f$. * @return true If the column has value zero. @@ -1209,25 +1163,34 @@ class Matrix { /** * @brief Assign operator. - * + * * @param other %Matrix to copy * @return Reference to this object. */ - Matrix& operator=(Matrix other); + Matrix& operator=(Matrix other) &; + /** + * @brief Assign operator. + * + * @param other %Matrix to move + * @return Reference to this object. + */ + Matrix& operator=(Matrix&& other) && noexcept; + /** * @brief Swap operator for two matrices. - * + * * @param matrix1 First matrix to swap. * @param matrix2 Second matrix to swap. */ - friend void swap(Matrix& matrix1, Matrix& matrix2) { + friend void swap(Matrix& matrix1, Matrix& matrix2) noexcept + { swap(matrix1.matrix_, matrix2.matrix_); std::swap(matrix1.colSettings_, matrix2.colSettings_); } - void print(Index startCol = 0, Index endCol = -1, Index startRow = 0, Index endRow = -1); // for debug + void print(); // for debug - //TODO: change the behaviour for boundary matrices. + // TODO: change the behaviour for boundary matrices. /** * @brief Returns the current barcode of the matrix. Available only if * @ref PersistenceMatrixOptions::has_column_pairings is true. @@ -1237,7 +1200,7 @@ class Matrix { * @warning For simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$), we assume that * @ref get_current_barcode is only called once the matrix is completed and won't be modified again. * - * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three information: * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. */ const Barcode& get_current_barcode(); @@ -1251,7 +1214,7 @@ class Matrix { * @ref get_current_barcode is only called once the matrix is completed and won't be modified again. * * @return A const reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three - * informations: the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. + * information: the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. */ const Barcode& get_current_barcode() const; @@ -1277,8 +1240,8 @@ class Matrix { * @param rowIndex2 Second @ref rowindex "row index" to swap. */ void swap_rows(Index rowIndex1, Index rowIndex2); - //TODO: find better name. And benchmark also to verify if it is really worth it to have this extra version in addition - //to vine_swap. + // TODO: find better name. And benchmark also to verify if it is really worth it to have this extra version in + // addition to vine_swap. /** * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true and if it is either a boundary * matrix or @ref PersistenceMatrixOptions::column_indexation_type is set to @ref Column_indexation_types::POSITION. @@ -1340,7 +1303,7 @@ class Matrix { */ Index vine_swap(Index columnIndex1, Index columnIndex2); - //TODO: Rethink the interface for representative cycles + // TODO: Rethink the interface for representative cycles /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Pre-computes * the representative cycles of the current state of the filtration represented by the matrix. It does not need to be @@ -1349,108 +1312,95 @@ class Matrix { * returned. */ void update_representative_cycles(); - // /** - // * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. - // * Returns all representative cycles of the current filtration. - // * - // * @return A const reference to the vector of representative cycles. - // */ - // const std::vector& get_representative_cycles(); - // /** - // * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. - // * Returns the cycle representing the given bar. - // * - // * @param bar A bar from the current barcode. - // * @return A const reference to the cycle representing @p bar. - // */ - // const Cycle& get_representative_cycle(const Bar& bar); - std::vector::ID_index> > > - get_representative_cycles_as_borders(bool detailed = false); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns all representative cycles of the current filtration. + * + * @return A const reference to the vector of representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns the cycle representing the given bar. + * + * @param bar A bar from the current barcode. + * @return A const reference to the cycle representing @p bar. + */ + const Cycle& get_representative_cycle(const Bar& bar); private: - using Underlying_matrix = - typename std::conditional< - isNonBasic, - typename std::conditional< - PersistenceMatrixOptions::is_of_boundary_type, - typename std::conditional< - PersistenceMatrixOptions::has_vine_update || - PersistenceMatrixOptions::can_retrieve_representative_cycles, - typename std::conditional< - PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || + using Underlying_matrix = std::conditional_t< + isNonBasic, + std::conditional_t< + PersistenceMatrixOptions::is_of_boundary_type, + std::conditional_t< + PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles, + std::conditional_t< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, - Master_RU_matrix, Id_to_index_overlay > - >::type, - typename std::conditional< - PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || + Master_RU_matrix, + Id_to_index_overlay + > >, + std::conditional_t< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, - Master_boundary_matrix, - Id_to_index_overlay > - >::type - >::type, - typename std::conditional< - PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER, - Master_chain_matrix, - typename std::conditional< - PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, - Position_to_index_overlay >, - Id_to_index_overlay > - >::type - >::type - >::type, - Master_base_matrix - >::type; + Master_boundary_matrix, + Id_to_index_overlay + > > >, + std::conditional_t< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER, + Master_chain_matrix, + std::conditional_t >, + Id_to_index_overlay + > > > >, + Master_base_matrix>; // Field_operators* operators_; // Entry_constructor* entryPool_; - Column_settings* colSettings_; //pointer because the of swap operator on matrix_ which also stores the pointer + Column_settings* colSettings_; // pointer because the of swap operator on matrix_ which also stores the pointer Underlying_matrix matrix_; static constexpr void _assert_options(); }; template -inline Matrix::Matrix() - : colSettings_(new Column_settings()), matrix_(colSettings_) +inline Matrix::Matrix() : colSettings_(new Column_settings()), matrix_(colSettings_) { static_assert( PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::has_column_pairings, - "When no barcode is recorded with vine swaps, comparaison functions for the columns have to be provided."); + "When no barcode is recorded with vine swaps, comparison functions for the columns have to be provided."); _assert_options(); } template template -inline Matrix::Matrix(const std::vector& columns, - Characteristic characteristic) - : colSettings_(new Column_settings(characteristic)), - matrix_(columns, colSettings_) +inline Matrix::Matrix(const std::vector& columns, Characteristic characteristic) + : colSettings_(new Column_settings(characteristic)), matrix_(columns, colSettings_) { static_assert(PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::has_column_pairings, - "When no barcode is recorded with vine swaps for chain matrices, comparaison functions for the columns " + "When no barcode is recorded with vine swaps for chain matrices, comparison functions for the columns " "have to be provided."); _assert_options(); } template inline Matrix::Matrix(unsigned int numberOfColumns, Characteristic characteristic) - : colSettings_(new Column_settings(characteristic)), - matrix_(numberOfColumns, colSettings_) + : colSettings_(new Column_settings(characteristic)), matrix_(numberOfColumns, colSettings_) { static_assert(PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::has_column_pairings, - "When no barcode is recorded with vine swaps for chain matrices, comparaison functions for the columns " + "When no barcode is recorded with vine swaps for chain matrices, comparison functions for the columns " "have to be provided."); _assert_options(); } template -inline Matrix::Matrix(const std::function& birthComparator, - const std::function& deathComparator) - : colSettings_(new Column_settings()), - matrix_(colSettings_, birthComparator, deathComparator) +inline Matrix::Matrix(const std::function& birthComparator, + const std::function& deathComparator) + : colSettings_(new Column_settings()), matrix_(colSettings_, birthComparator, deathComparator) { static_assert( !PersistenceMatrixOptions::is_of_boundary_type && PersistenceMatrixOptions::has_vine_update && @@ -1492,32 +1442,30 @@ inline Matrix::Matrix(unsigned int numberOfColumns, template inline Matrix::Matrix(const Matrix& matrixToCopy) - : colSettings_(new Column_settings(*matrixToCopy.colSettings_)), - matrix_(matrixToCopy.matrix_, colSettings_) + : colSettings_(new Column_settings(*matrixToCopy.colSettings_)), matrix_(matrixToCopy.matrix_, colSettings_) { _assert_options(); } template inline Matrix::Matrix(Matrix&& other) noexcept - : colSettings_(std::exchange(other.colSettings_, nullptr)), - matrix_(std::move(other.matrix_)) + : colSettings_(std::exchange(other.colSettings_, nullptr)), matrix_(std::move(other.matrix_)) { _assert_options(); } template -inline Matrix::~Matrix() +inline Matrix::~Matrix() { - matrix_.reset(colSettings_); + matrix_.reset(colSettings_); // to avoid crashes at destruction, all columns have to be destroyed first delete colSettings_; } template -inline void Matrix::set_characteristic(Characteristic characteristic) +inline void Matrix::set_characteristic(Characteristic characteristic) { if constexpr (!PersistenceMatrixOptions::is_z2) { - if (colSettings_->operators.get_characteristic() != static_cast(-1)) { + if (colSettings_->operators.get_characteristic() != Field_operators::nullCharacteristic) { std::cerr << "Warning: Characteristic already initialised. Changing it could lead to incoherences in the matrix " "as the modulo was already applied to values in existing columns."; } @@ -1528,10 +1476,10 @@ inline void Matrix::set_characteristic(Characteristic template template -inline void Matrix::insert_column(const Container& column) +inline void Matrix::insert_column(const Container& column) { - if constexpr (!PersistenceMatrixOptions::is_z2){ - GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + if constexpr (!PersistenceMatrixOptions::is_z2) { + GUDHI_CHECK(colSettings_->operators.get_characteristic() != Field_operators::nullCharacteristic, std::logic_error("Matrix::insert_column - Columns cannot be initialized if the coefficient field " "characteristic is not specified.")); } @@ -1544,10 +1492,10 @@ inline void Matrix::insert_column(const Container& col template template -inline void Matrix::insert_column(const Container& column, Index columnIndex) +inline void Matrix::insert_column(const Container& column, Index columnIndex) { - if constexpr (!PersistenceMatrixOptions::is_z2){ - GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + if constexpr (!PersistenceMatrixOptions::is_z2) { + GUDHI_CHECK(colSettings_->operators.get_characteristic() != Field_operators::nullCharacteristic, std::logic_error("Matrix::insert_column - Columns cannot be initialized if the coefficient field " "characteristic is not specified.")); } @@ -1561,11 +1509,12 @@ inline void Matrix::insert_column(const Container& col template template -inline typename Matrix::Insertion_return -Matrix::insert_boundary(const Boundary_range& boundary, Dimension dim) +inline typename Matrix::Insertion_return Matrix::insert_boundary( + const Boundary_range& boundary, + Dimension dim) { - if constexpr (!PersistenceMatrixOptions::is_z2){ - GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + if constexpr (!PersistenceMatrixOptions::is_z2) { + GUDHI_CHECK(colSettings_->operators.get_characteristic() != Field_operators::nullCharacteristic, std::logic_error("Matrix::insert_boundary - Columns cannot be initialized if the coefficient field " "characteristic is not specified.")); } @@ -1580,16 +1529,14 @@ Matrix::insert_boundary(const Boundary_range& boundary template template inline typename Matrix::Insertion_return -Matrix::insert_boundary(ID_index cellIndex, - const Boundary_range& boundary, - Dimension dim) +Matrix::insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim) { - if constexpr (!PersistenceMatrixOptions::is_z2){ - GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + if constexpr (!PersistenceMatrixOptions::is_z2) { + GUDHI_CHECK(colSettings_->operators.get_characteristic() != Field_operators::nullCharacteristic, std::logic_error("Matrix::insert_boundary - Columns cannot be initialized if the coefficient field " "characteristic is not specified.")); } - + static_assert(isNonBasic, "Only enabled for non-basic matrices."); if constexpr (!PersistenceMatrixOptions::is_of_boundary_type && PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER) @@ -1607,14 +1554,15 @@ inline typename Matrix::Returned_column& Matrix inline const typename Matrix::Column& Matrix::get_column( - Index columnIndex) const + Index columnIndex) const { return matrix_.get_column(columnIndex); } template inline const typename Matrix::Column& Matrix::get_column( - Index columnIndex, bool inR) + Index columnIndex, + bool inR) { // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. static_assert( @@ -1646,7 +1594,8 @@ inline const typename Matrix::Row& Matrix inline const typename Matrix::Row& Matrix::get_row( - ID_index rowIndex, bool inR) + ID_index rowIndex, + bool inR) { static_assert(PersistenceMatrixOptions::has_row_access, "'get_row' is not available for the chosen options."); // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. @@ -1660,7 +1609,7 @@ inline const typename Matrix::Row& Matrix -inline void Matrix::remove_column(Index columnIndex) +inline void Matrix::remove_column(Index columnIndex) { static_assert(PersistenceMatrixOptions::has_map_column_container && !isNonBasic && !PersistenceMatrixOptions::has_column_compression, @@ -1670,7 +1619,7 @@ inline void Matrix::remove_column(Index columnIndex) } template -inline void Matrix::erase_empty_row(ID_index rowIndex) +inline void Matrix::erase_empty_row(ID_index rowIndex) { static_assert( !isNonBasic || PersistenceMatrixOptions::is_of_boundary_type || PersistenceMatrixOptions::has_removable_rows, @@ -1680,7 +1629,7 @@ inline void Matrix::erase_empty_row(ID_index rowIndex) } template -inline void Matrix::remove_maximal_cell(Index columnIndex) +inline void Matrix::remove_maximal_cell(Index columnIndex) { static_assert(PersistenceMatrixOptions::has_removable_columns, "'remove_maximal_cell(ID_index)' is not available for the chosen options."); @@ -1708,7 +1657,7 @@ inline void Matrix::remove_maximal_cell(ID_index cellI } template -inline void Matrix::remove_last() +inline void Matrix::remove_last() { static_assert(PersistenceMatrixOptions::has_removable_columns || !isNonBasic, "'remove_last' is not available for the chosen options."); @@ -1722,8 +1671,7 @@ inline void Matrix::remove_last() } template -inline typename Matrix::Dimension Matrix::get_max_dimension() - const +inline typename Matrix::Dimension Matrix::get_max_dimension() const { static_assert(isNonBasic, "'get_max_dimension' is not available for the chosen options."); @@ -1731,7 +1679,7 @@ inline typename Matrix::Dimension Matrix -inline typename Matrix::Index Matrix::get_number_of_columns() const +inline typename Matrix::Index Matrix::get_number_of_columns() const { return matrix_.get_number_of_columns(); } @@ -1748,7 +1696,8 @@ inline typename Matrix::Dimension Matrix template inline std::enable_if_t > Matrix::add_to( - Integer_index sourceColumnIndex, Integer_index targetColumnIndex) + Integer_index sourceColumnIndex, + Integer_index targetColumnIndex) { matrix_.add_to(sourceColumnIndex, targetColumnIndex); } @@ -1756,7 +1705,8 @@ inline std::enable_if_t > Matrix template inline std::enable_if_t > Matrix::add_to( - const Entry_range& sourceColumn, Index targetColumnIndex) + const Entry_range& sourceColumn, + Index targetColumnIndex) { static_assert(!isNonBasic, "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " @@ -1767,21 +1717,26 @@ inline std::enable_if_t > Matrix template -inline std::enable_if_t > Matrix::multiply_target_and_add_to( - Integer_index sourceColumnIndex, int coefficient, Integer_index targetColumnIndex) +inline std::enable_if_t > +Matrix::multiply_target_and_add_to(Integer_index sourceColumnIndex, + int coefficient, + Integer_index targetColumnIndex) { if constexpr (PersistenceMatrixOptions::is_z2) { // coef will be converted to bool, because of Element matrix_.multiply_target_and_add_to(sourceColumnIndex, coefficient % 2, targetColumnIndex); } else { - matrix_.multiply_target_and_add_to(sourceColumnIndex, colSettings_->operators.get_value(coefficient), targetColumnIndex); + matrix_.multiply_target_and_add_to( + sourceColumnIndex, colSettings_->operators.get_value(coefficient), targetColumnIndex); } } template template inline std::enable_if_t > Matrix::multiply_target_and_add_to( - const Entry_range& sourceColumn, int coefficient, Index targetColumnIndex) + const Entry_range& sourceColumn, + int coefficient, + Index targetColumnIndex) { static_assert(!isNonBasic, "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " @@ -1797,21 +1752,26 @@ inline std::enable_if_t > Matrix template -inline std::enable_if_t > Matrix::multiply_source_and_add_to( - int coefficient, Integer_index sourceColumnIndex, Integer_index targetColumnIndex) +inline std::enable_if_t > +Matrix::multiply_source_and_add_to(int coefficient, + Integer_index sourceColumnIndex, + Integer_index targetColumnIndex) { if constexpr (PersistenceMatrixOptions::is_z2) { // coef will be converted to bool, because of Element matrix_.multiply_source_and_add_to(coefficient % 2, sourceColumnIndex, targetColumnIndex); } else { - matrix_.multiply_source_and_add_to(colSettings_->operators.get_value(coefficient), sourceColumnIndex, targetColumnIndex); + matrix_.multiply_source_and_add_to( + colSettings_->operators.get_value(coefficient), sourceColumnIndex, targetColumnIndex); } } template template inline std::enable_if_t > Matrix::multiply_source_and_add_to( - int coefficient, const Entry_range& sourceColumn, Index targetColumnIndex) + int coefficient, + const Entry_range& sourceColumn, + Index targetColumnIndex) { static_assert(!isNonBasic, "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " @@ -1826,7 +1786,7 @@ inline std::enable_if_t > Matrix -inline void Matrix::zero_entry(Index columnIndex, ID_index rowIndex) +inline void Matrix::zero_entry(Index columnIndex, ID_index rowIndex) { static_assert(PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_column_compression, "'zero_entry' is not available for the chosen options."); @@ -1835,7 +1795,7 @@ inline void Matrix::zero_entry(Index columnIndex, ID_i } template -inline void Matrix::zero_entry(Index columnIndex, ID_index rowIndex, bool inR) +inline void Matrix::zero_entry(Index columnIndex, ID_index rowIndex, bool inR) { // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. static_assert( @@ -1848,7 +1808,7 @@ inline void Matrix::zero_entry(Index columnIndex, ID_i } template -inline void Matrix::zero_column(Index columnIndex) +inline void Matrix::zero_column(Index columnIndex) { static_assert(PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_column_compression, "'zero_column' is not available for the chosen options."); @@ -1857,7 +1817,7 @@ inline void Matrix::zero_column(Index columnIndex) } template -inline void Matrix::zero_column(Index columnIndex, bool inR) +inline void Matrix::zero_column(Index columnIndex, bool inR) { // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. static_assert( @@ -1870,13 +1830,13 @@ inline void Matrix::zero_column(Index columnIndex, boo } template -inline bool Matrix::is_zero_entry(Index columnIndex, ID_index rowIndex) +inline bool Matrix::is_zero_entry(Index columnIndex, ID_index rowIndex) { return matrix_.is_zero_entry(columnIndex, rowIndex); } template -inline bool Matrix::is_zero_entry(Index columnIndex, ID_index rowIndex, bool inR) const +inline bool Matrix::is_zero_entry(Index columnIndex, ID_index rowIndex, bool inR) const { // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. static_assert( @@ -1889,13 +1849,13 @@ inline bool Matrix::is_zero_entry(Index columnIndex, I } template -inline bool Matrix::is_zero_column(Index columnIndex) +inline bool Matrix::is_zero_column(Index columnIndex) { return matrix_.is_zero_column(columnIndex); } template -inline bool Matrix::is_zero_column(Index columnIndex, bool inR) +inline bool Matrix::is_zero_column(Index columnIndex, bool inR) { // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. static_assert( @@ -1929,7 +1889,7 @@ inline typename Matrix::ID_index Matrix -inline Matrix& Matrix::operator=(Matrix other) +inline Matrix& Matrix::operator=(Matrix other) & { swap(matrix_, other.matrix_); std::swap(colSettings_, other.colSettings_); @@ -1938,14 +1898,22 @@ inline Matrix& Matrix::opera } template -inline void Matrix::print(Index startCol, Index endCol, Index startRow, Index endRow) +inline Matrix& Matrix::operator=(Matrix&& other) && noexcept +{ + matrix_ = std::move(other.matrix_); + colSettings_ = std::exchange(other.colSettings_, nullptr); + + return *this; +} + +template +inline void Matrix::print() { - return matrix_.print(startCol, endCol, startRow, endRow); + return matrix_.print(); } template -inline const typename Matrix::Barcode& -Matrix::get_current_barcode() +inline const typename Matrix::Barcode& Matrix::get_current_barcode() { static_assert(PersistenceMatrixOptions::has_column_pairings, "This method was not enabled."); @@ -1953,8 +1921,8 @@ Matrix::get_current_barcode() } template -inline const typename Matrix::Barcode& -Matrix::get_current_barcode() const +inline const typename Matrix::Barcode& Matrix::get_current_barcode() + const { static_assert(PersistenceMatrixOptions::has_column_pairings, "This method was not enabled."); static_assert( @@ -1967,7 +1935,7 @@ Matrix::get_current_barcode() const } template -inline void Matrix::swap_columns(Index columnIndex1, Index columnIndex2) +inline void Matrix::swap_columns(Index columnIndex1, Index columnIndex2) { static_assert( (!isNonBasic && !PersistenceMatrixOptions::has_column_compression) || @@ -1978,7 +1946,7 @@ inline void Matrix::swap_columns(Index columnIndex1, I } template -inline void Matrix::swap_rows(Index rowIndex1, Index rowIndex2) +inline void Matrix::swap_rows(Index rowIndex1, Index rowIndex2) { static_assert( (!isNonBasic && !PersistenceMatrixOptions::has_column_compression) || @@ -1989,7 +1957,7 @@ inline void Matrix::swap_rows(Index rowIndex1, Index r } template -inline bool Matrix::vine_swap_with_z_eq_1_case(Pos_index index) +inline bool Matrix::vine_swap_with_z_eq_1_case(Pos_index index) { static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION || @@ -2001,7 +1969,8 @@ inline bool Matrix::vine_swap_with_z_eq_1_case(Pos_ind template inline typename Matrix::Index Matrix::vine_swap_with_z_eq_1_case( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::IDENTIFIER || @@ -2013,7 +1982,7 @@ inline typename Matrix::Index Matrix -inline bool Matrix::vine_swap(Pos_index index) +inline bool Matrix::vine_swap(Pos_index index) { static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION || @@ -2024,8 +1993,8 @@ inline bool Matrix::vine_swap(Pos_index index) } template -inline typename Matrix::Index Matrix::vine_swap( - Index columnIndex1, Index columnIndex2) +inline typename Matrix::Index Matrix::vine_swap(Index columnIndex1, + Index columnIndex2) { static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::IDENTIFIER || @@ -2036,38 +2005,30 @@ inline typename Matrix::Index Matrix -inline void Matrix::update_representative_cycles() +inline void Matrix::update_representative_cycles() { static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); matrix_.update_representative_cycles(); } -// template -// inline const std::vector::Cycle>& -// Matrix::get_representative_cycles() -// { -// static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); -// return matrix_.get_representative_cycles(); -// } - -// template -// inline const typename Matrix::Cycle& -// Matrix::get_representative_cycle(const Bar& bar) -// { -// static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); -// return matrix_.get_representative_cycle(bar); -// } +template +inline const std::vector::Cycle>& +Matrix::get_representative_cycles() +{ + static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); + return matrix_.get_representative_cycles(); +} template -inline std::vector::ID_index> > > -Matrix::get_representative_cycles_as_borders(bool detailed) { - static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles && PersistenceMatrixOptions::is_z2, - "This method was not enabled."); - return matrix_.get_representative_cycles_as_borders(detailed); +inline const typename Matrix::Cycle& +Matrix::get_representative_cycle(const Bar& bar) +{ + static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); + return matrix_.get_representative_cycle(bar); } template -inline constexpr void Matrix::_assert_options() +constexpr void Matrix::_assert_options() { static_assert( PersistenceMatrixOptions::column_type != Column_types::HEAP || !PersistenceMatrixOptions::has_row_access, diff --git a/multipers/gudhi/gudhi/Multi_critical_filtration.h b/multipers/gudhi/gudhi/Multi_critical_filtration.h deleted file mode 100644 index 556cc33b..00000000 --- a/multipers/gudhi/gudhi/Multi_critical_filtration.h +++ /dev/null @@ -1,1038 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2023 Inria - * - * Modification(s): - * - 2024/08 Hannah Schreiber: Optimization and correction + numeric_limits + doc - * - YYYY/MM Author: Description of the modification - */ - -/** - * @file Multi_critical_filtration.h - * @author David Loiseaux - * @brief Contains the @ref Gudhi::multi_filtration::Multi_critical_filtration class. - */ - -#ifndef MULTI_CRITICAL_FILTRATIONS_H_ -#define MULTI_CRITICAL_FILTRATIONS_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Gudhi { -namespace multi_filtration { - -/** - * @class Multi_critical_filtration multi_critical_filtration.h gudhi/multi_critical_filtration.h - * @ingroup multi_filtration - * - * @brief Class encoding the different generators, i.e., apparition times, of a \f$k\f$-critical - * \f$\mathbb R^n\f$-filtration value, e.g., the filtration of a simplex, or of the algebraic generator of a module - * presentation. The class can be used as a vector whose indices correspond each to a generator, i.e., a one-critical - * filtration value. Then, the indices of each generator correspond to a particular parameter. - * E.g., \f$ f[i][p] \f$ will be \f$ p^{\textit{th}} \f$ parameter of the \f$ i^{\textit{th}} \f$ generator - * of this filtration value with @ref Multi_critical_filtration \f$ f \f$. - * - * @details Overloads `std::numeric_limits` such that: - * - `std::numeric_limits >::has_infinity` returns `true`, - * - `std::numeric_limits >::infinity()` returns - * @ref Multi_critical_filtration::inf() "", - * - `std::numeric_limits >::minus_infinity()` returns - * @ref Multi_critical_filtration::minus_inf() "", - * - `std::numeric_limits >::max()` throws, - * - `std::numeric_limits >::max(g,n)` returns a @ref Multi_critical_filtration - * with `g` generators of `n` parameters evaluated at value `std::numeric_limits::max()`, - * - `std::numeric_limits >::quiet_NaN()` returns - * @ref Multi_critical_filtration::nan() "". - * - * Multi-critical filtrations are filtrations such that the lifetime of each object is union of positive cones in - * \f$\mathbb R^n\f$, e.g., - * - \f$ \{ x \in \mathbb R^2 : x \ge (1,2)\} \cap \{ x \in \mathbb R^2 : x \ge (2,1)\} \f$ is finitely critical, - * and more particularly 2-critical, while - * - \f$ \{ x \in \mathbb R^2 : x \ge \mathrm{epigraph}(y \mapsto e^{-y})\} \f$ is not. - * - * The particular case of 1-critical filtrations is handled by @ref One_critical_filtration "". - * - * @tparam T Arithmetic type of an entry for one parameter of a filtration value. Has to be **signed** and - * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, - * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. - * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` - * can simply throw. Examples are the native types `double`, `float` and `int`. - * @tparam co If `true`, reverses the poset order, i.e., the order \f$ \le \f$ in \f$ \mathbb R^n \f$ becomes - * \f$ \ge \f$. - */ -template -class Multi_critical_filtration { - public: - /** - * @brief Type of the origin of a "lifetime cone". Common with @ref One_critical_filtration "". - */ - using Generator = One_critical_filtration; - using Generators = std::vector; /**< Container type for the filtration values. */ - using iterator = typename Generators::iterator; /**< Iterator type for the generator container. */ - using const_iterator = typename Generators::const_iterator; /**< Const iterator type for the generator container. */ - - // CONSTRUCTORS - - /** - * @brief Default constructor. The constructed value will be either at infinity if `co` is true or at minus infinity - * if `co` is false. - */ - Multi_critical_filtration() : multi_filtration_(_get_default_filtration_value()) {}; - /** - * @brief Constructs a filtration value with one generator and @p n parameters. - * All parameters will be initialized at -inf if `co` is false and at inf if `co` is true. - * - * @warning The generator `{-inf, -inf, ...}`/`{inf, inf, ...}` with \f$ n > 1 \f$ entries is not considered as - * "(minus) infinity" (the resp. methods @ref is_minus_inf() and @ref is_plus_inf() "", as well as the ones of the - * generator, will not return true). The `-inf/inf` are just meant as placeholders, at least one entry should be - * modified by the user. - * Otherwise, either use the static methods @ref minus_inf() or @ref inf(), or set @p n to 1 instead. - * - * @param n Number of parameters. - */ - Multi_critical_filtration(int n) : multi_filtration_(1, Generator(n, _get_default_value())) {}; - /** - * @brief Constructs a filtration value with one generator and @p n parameters. All parameters will be initialized - * with @p value. - * - * @warning If @p value is `inf`, `-inf`, or `NaN`, the generator `{value, value, ...}` with \f$ n > 1 \f$ entries - * is not wrong but will not be considered as respectively "infinity", "minus infinity" or "NaN" (the corresponding - * methods @ref is_plus_inf(), @ref is_minus_inf() and @ref is_nan() will return false). For this purpose, please use - * the static methods @ref inf(), @ref minus_inf() and @ref nan() instead. - * - * @param n Number of parameters. - * @param value Value which will be used for each entry. - */ - Multi_critical_filtration(int n, T value) : multi_filtration_(1, Generator(n, value)) {}; - /** - * @brief Constructs a filtration value with one generator which will be initialzed by the given initializer list. - * - * @param init Initializer list with values for each parameter. - */ - Multi_critical_filtration(std::initializer_list init) : multi_filtration_(1, Generator{init}) {}; - /** - * @brief Constructs a filtration value with one generator which will be initialzed by the given vector. - * - * @param v Vector with values for each parameter. - */ - Multi_critical_filtration(const std::vector &v) : multi_filtration_(1, Generator{v}) {}; - /** - * @brief Constructs a filtration value with one generator to which the given vector is moved to. - * - * @param v Vector with values for each parameter. - */ - Multi_critical_filtration(std::vector &&v) : multi_filtration_(1, Generator{std::move(v)}) {}; - /** - * @brief Constructs filtration value with as many generators than elements in the given vector and initialize - * them with them. - * If the vector is empty, then the filtration value is either initialized at infinity if `co` is true or at - * minus infinity if `co` is false. - * @pre All generators in the vector have to have the same number of parameters, i.e., size. - * Furthermore, the generators have to be a minimal generating set. - * - * @warning If the set of generators is not minimal or not sorted, the behaviour of most methods is undefined. - * It is possible to call @ref simplify() after construction if there is a doubt to ensure this property. - * - * @param v Vector of generators. - */ - Multi_critical_filtration(const std::vector &v) - : multi_filtration_(v.empty() ? _get_default_filtration_value() : v) {}; - /** - * @brief Constructs filtration value with as many generators than elements in the given vector and moves those - * elements to initialize the generators. - * If the vector is empty, then the filtration value is either initialized at infinity if `co` is true or at - * minus infinity if `co` is false. - * @pre All generators in the vector have to have the same number of parameters, i.e., size. - * Furthermore, the generators have to be a minimal generating set. - * - * @warning If the set of generators is not minimal or not sorted, the behaviour of most methods is undefined. - * It is possible to call @ref simplify() after construction if there is a doubt to ensure this property. - * - * @param v Vector of generators. - */ - Multi_critical_filtration(std::vector &&v) - : multi_filtration_(v.empty() ? _get_default_filtration_value() : std::move(v)) {}; - /** - * @brief Constructs a filtration value with one generator initialzed by the range given by the begin and end - * iterators. - * - * @param it_begin Start of the range. - * @param it_end End of the range. - */ - Multi_critical_filtration(typename std::vector::iterator it_begin, typename std::vector::iterator it_end) - : multi_filtration_(Generators(1, {it_begin, it_end})) {}; - /** - * @brief Constructs a filtration value with one generator initialzed by the range given by the begin and end - * const iterators. - * - * @param it_begin Start of the range. - * @param it_end End of the range. - */ - Multi_critical_filtration(typename std::vector::const_iterator it_begin, - typename std::vector::const_iterator it_end) - : multi_filtration_(Generator(1, {it_begin, it_end})) {}; - - // VECTOR-LIKE - - using value_type = T; /**< Entry type. */ - - /** - * @brief Standard operator[]. - */ - Generator &operator[](std::size_t i) { return multi_filtration_[i]; } - - /** - * @brief Standard operator[] const. - */ - const Generator &operator[](std::size_t i) const { return multi_filtration_[i]; } - - /** - * @brief Returns begin iterator of the generator range. - * - * @warning If the generator is modified and the new set of generators is not minimal or not sorted, the behaviour - * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to - * ensure this property. - */ - iterator begin() { return multi_filtration_.begin(); } - - /** - * @brief Returns end iterator of the generator range. - * - * @warning If the generator is modified and the new set of generators is not minimal or not sorted, the behaviour - * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to - * ensure this property. - */ - iterator end() { return multi_filtration_.end(); } - - /** - * @brief Returns begin const iterator of the generator range. - */ - const_iterator begin() const { return multi_filtration_.begin(); } - - /** - * @brief Returns end const iterator of the generator range. - */ - const_iterator end() const { return multi_filtration_.end(); } - - /** - * @brief Reserves space for the given number of generators in the underlying container. - * - * @param n Number of generators. - */ - void reserve(std::size_t n) { multi_filtration_.reserve(n); } - - // CONVERTERS - - /** - * @brief Casts the object into the type of a generator. - * @pre The filtration value is 1-critical. If there are more than one generator, only the first will be preserved - * and if there is no generator, the method will segfault. - */ - operator Generator() { - GUDHI_CHECK(num_generators() == 1, - "Casting a " + std::to_string(num_generators()) + - "-critical filtration value into an 1-critical filtration value."); - return multi_filtration_[0]; - } - - /** - * @brief Casts the object into the type of a generator. - * @pre The filtration value is 1-critical. If there are more than one generator, only the first will be preserved - * and if there is no generator, the method will segfault. - */ - operator const Generator() const { - GUDHI_CHECK(num_generators() == 1, - "Casting a " + std::to_string(num_generators()) + - "-critical filtration value into an 1-critical filtration value."); - return multi_filtration_[0]; - } - - // like numpy - /** - * @brief Returns a copy with entries casted into the type given as template parameter. - * - * @tparam U New type for the entries. - * @return Copy with new entry type. - */ - template - Multi_critical_filtration as_type() const { - std::vector> out(num_generators()); - for (std::size_t i = 0u; i < num_generators(); ++i) { - out[i] = multi_filtration_[i].template as_type(); - } - return Multi_critical_filtration(std::move(out)); - } - - // ACCESS - - /** - * @brief Returns a reference to the underlying container storing the generators. - * - * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour - * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to - * ensure this property. - */ - const Generators &get_underlying_container() const { return multi_filtration_; } - - /** - * @brief Returns the number of parameters. - */ - std::size_t num_parameters() const { return multi_filtration_[0].num_parameters(); } - - /** - * @brief Returns the number of generators. - */ - std::size_t num_generators() const { return multi_filtration_.size(); } - - /** - * @brief Returns a filtration value for which @ref is_plus_inf() returns `true`. - * - * @return Infinity. - */ - constexpr static Multi_critical_filtration inf() { return Multi_critical_filtration(Generator::inf()); } - - /** - * @brief Returns a filtration value for which @ref is_minus_inf() returns `true`. - * - * @return Minus infinity. - */ - constexpr static Multi_critical_filtration minus_inf() { return Multi_critical_filtration(Generator::minus_inf()); } - - /** - * @brief Returns a filtration value for which @ref is_nan() returns `true`. - * - * @return NaN. - */ - constexpr static Multi_critical_filtration nan() { return Multi_critical_filtration(Generator::nan()); } - - constexpr static bool is_multicritical() { return true; } - - // DESCRIPTORS - - // TODO: Accept {{-inf, -inf, ...},...} / {{inf, inf, ...},...} / {{NaN, NaN, ...},...} as resp. -inf / inf / NaN. - - /** - * @brief Returns `true` if and only if the filtration value is considered as infinity. - */ - bool is_plus_inf() const { return multi_filtration_.size() == 1 && multi_filtration_[0].is_plus_inf(); } - - /** - * @brief Returns `true` if and only if the filtration value is considered as minus infinity. - */ - bool is_minus_inf() const { return multi_filtration_.size() == 1 && multi_filtration_[0].is_minus_inf(); } - - /** - * @brief Returns `true` if and only if the filtration value is considered as NaN. - */ - bool is_nan() const { return multi_filtration_.size() == 1 && multi_filtration_[0].is_nan(); } - - /** - * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as infinity, - * minus infinity or NaN. - */ - bool is_finite() const { - if (multi_filtration_.size() > 1) return true; - return multi_filtration_[0].is_finite(); - } - - // COMPARAISON OPERATORS - - // TODO : this costs a lot... optimize / cheat in some way for python ? - - /** - * @brief Returns `true` if and only if the positive cones generated by @p b are strictly contained in the - * positive cones generated by @p a. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false - * does **not** imply \f$ a == b \f$. - */ - friend bool operator<(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { - for (std::size_t i = 0u; i < b.multi_filtration_.size(); ++i) { - // for each generator in b, verify if it is strictly in the cone of at least one generator of a - bool isContained = false; - for (std::size_t j = 0u; j < a.multi_filtration_.size() && !isContained; ++j) { - // lexicographical order, so if a[j][0] dom b[j][0], than a[j'] can never strictly contain b[i] for all j' > j. - if (_first_dominates(a.multi_filtration_[j], b.multi_filtration_[i])) return false; - isContained = _strictly_contains(a.multi_filtration_[j], b.multi_filtration_[i]); - } - if (!isContained) return false; - } - return true; - } - - /** - * @brief Returns `true` if and only if the positive cones generated by @p a are strictly contained in the - * positive cones generated by @p b. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false - * does **not** imply \f$ a == b \f$. - */ - friend bool operator>(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { return b < a; } - - /** - * @brief Returns `true` if and only if the positive cones generated by @p b are contained in or are (partially) - * equal to the positive cones generated by @p a. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return - * `false`. - */ - friend bool operator<=(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { - // check if this curves is below other's curve - // ie for each guy in this, check if there is a guy in other that dominates him - for (std::size_t i = 0u; i < b.multi_filtration_.size(); ++i) { - // for each generator in b, verify if it is in the cone of at least one generator of a - bool isContained = false; - for (std::size_t j = 0u; j < a.multi_filtration_.size() && !isContained; ++j) { - // lexicographical order, so if a[j][0] strictly dom b[j][0], than a[j'] can never contain b[i] for all j' > j. - if (_first_strictly_dominates(a.multi_filtration_[j], b.multi_filtration_[i])) return false; - isContained = _contains(a.multi_filtration_[j], b.multi_filtration_[i]); - } - if (!isContained) return false; - } - return true; - } - - /** - * @brief Returns `true` if and only if the positive cones generated by @p a are contained in or are (partially) - * equal to the positive cones generated by @p b. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return - * `false`. - */ - friend bool operator>=(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { return b <= a; } - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is equal to \f$ b[i] \f$. - */ - friend bool operator==(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { - // assumes lexicographical order for both - return a.multi_filtration_ == b.multi_filtration_; - } - - /** - * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. - */ - friend bool operator!=(const Multi_critical_filtration &a, const Multi_critical_filtration &b) { return !(a == b); } - - // MODIFIERS - - /** - * @brief Sets the number of generators. If there were less generators before, new empty generators are constructed. - * If there were more generators before, the exceed of generators is destroyed (any generator with index higher or - * equal than @p n to be more precise). If @p n is zero, the methods does nothing. A filtration value should never - * be empty. - * - * @warning All empty generators have 0 parameters. This can be problematic for some methods if there are also - * non empty generators in the container. Make sure to fill them with real generators or to remove them before - * using those methods. - * - * @warning Be sure to call @ref simplify if necessary after setting all the generators. Most methods will have an - * undefined behaviour if the set of generators is not minimal or sorted. - * - * @param n New number of generators. - */ - void set_num_generators(std::size_t n) { - if (n == 0) return; - multi_filtration_.resize(n); - } - - /** - * @brief Sets all generators to the least common upper bound between the current generator value and the given value. - * - * More formally, it pushes the current filtration value to the cone \f$ \{ y \in \mathbb R^n : y \ge x \} \f$ - * originating in \f$ x \f$. The resulting values corresponds to the generators of the intersection of this cone - * with the union of positive cones generated by the old generators. - * - * @param x The target filtration value towards which to push. - */ - void push_to_least_common_upper_bound(const Generator &x) { - if (this->is_plus_inf() || this->is_nan() || x.is_nan() || x.is_minus_inf()) return; - - GUDHI_CHECK(x.is_plus_inf() || x.num_parameters() == multi_filtration_[0].num_parameters() || !is_finite(), - "Pushing to a filtration value with different number of parameters."); - - if (x.is_plus_inf() || this->is_minus_inf()) { - multi_filtration_ = {x}; - return; - } - for (auto &g : *this) { - g.push_to_least_common_upper_bound(x); - } - - simplify(); - } - - /** - * @brief Sets all generators to the greatest common lower bound between the current generator value and the given - * value. - * - * More formally, it pulls the current filtration value to the cone \f$ \{ y \in \mathbb R^n : y \le x \} \f$ - * originating in \f$ x \f$. The resulting values corresponds to the generators of the intersection of this cone - * with the union of negative cones generated by the old generators. - * - * @param x The target filtration value towards which to pull. - */ - void pull_to_greatest_common_lower_bound(const Generator &x) { - if (x.is_plus_inf() || this->is_nan() || x.is_nan() || this->is_minus_inf()) return; - - GUDHI_CHECK(x.is_minus_inf() || x.num_parameters() == multi_filtration_[0].num_parameters() || !is_finite(), - "Pulling to a filtration value with different number of parameters."); - - if (this->is_plus_inf() || x.is_minus_inf()) { - multi_filtration_ = {x}; - return; - } - for (auto &g : *this) { - g.pull_to_greatest_common_lower_bound(x); - } - - simplify(); - } - - /** - * @brief Adds the given generator to the filtration value such that the sets remains minimal. - * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that - * old generators disappear if they are overshadowed by the new one. - * @pre If all are finite, the new generator has to have the same number of parameters than the others. - * - * @param x New generator to add. - * @return true If and only if the generator is actually added to the set of generators. - * @return false Otherwise. - */ - bool add_generator(const Generator &x) { - GUDHI_CHECK(x.num_parameters() == multi_filtration_[0].num_parameters() || !is_finite() || !x.is_finite(), - "Cannot add a generator with different number of parameters."); - - std::size_t end = multi_filtration_.size(); - - if (_generator_can_be_added(x, 0, end)) { - multi_filtration_.resize(end); - multi_filtration_.push_back(x); - std::sort(multi_filtration_.begin(), multi_filtration_.end(), Is_strictly_smaller_lexicographically()); - return true; - } - - return false; - } - - /** - * @brief Adds the given generator to the filtration value without any verifications or simplifications. - * - * @warning If the resulting set of generators is not minimal after modification, some methods will have an - * undefined behaviour. Be sure to call @ref simplify() before using them. - * - * @param x - */ - void add_guaranteed_generator(const Generator &x) { multi_filtration_.push_back(x); } - - /* - * Same as `compute_coordinates_in_grid`, but does the operation in-place. - */ - - /** - * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to - * the nearest upper bound value with the same parameter in the grid and the new generators are simplified and - * ordered. Otherwise, the entries are set to the indices of those nearest upper bound values. In this case, - * no simplification or sort are done, such that the new coordinates have a one by one correspondence with the - * positions of the old generators. - * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index - * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator. - * The ranges correspond to the possible values of the parameters, ordered by increasing value, forming therefore - * all together a 2D grid. - * - * @tparam oned_array A range of values convertible into `T` ordered by increasing value. Has to implement - * a begin, end and operator[] method. - * @param grid Vector of @p oned_array with size at least number of filtration parameters. - * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, - * the values are set to the values at the coordinates of the projection. - */ - template - void project_onto_grid(const std::vector &grid, bool coordinate = true) { - GUDHI_CHECK(grid.size() >= num_parameters(), - "The grid should not be smaller than the number of parameters in the filtration value."); - - for (auto &x : multi_filtration_) { - x.project_onto_grid(grid, coordinate); - } - - if (!coordinate) simplify(); - } - - /** - * @brief Removes all empty generators from the filtration value. If @p include_infinities is true, it also - * removes the generators at infinity or minus infinity. - * If the set of generators is empty after removals, it is set to minus infinity if `co` is false or to infinity - * if `co` is true. - * - * @param include_infinities If true, removes also infinity values. - */ - void remove_empty_generators(bool include_infinities = false) { - multi_filtration_.erase(std::remove_if(multi_filtration_.begin(), - multi_filtration_.end(), - [include_infinities](const Generator &a) { - return a.empty() || - ((include_infinities) && (a.is_plus_inf() || a.is_minus_inf())); - }), - multi_filtration_.end()); - std::sort(multi_filtration_.begin(), multi_filtration_.end(), Is_strictly_smaller_lexicographically()); - if (multi_filtration_.empty()) multi_filtration_.push_back(Generator{_get_default_value()}); - } - - /** - * @brief Simplifies the current set of generators such that it becomes minimal. Also orders it in increasing - * lexicographical order. Only necessary if generators were added "by hand" without verification either trough the - * constructor or with @ref add_guaranteed_generator "", etc. - */ - void simplify() { - std::size_t end = 0; - - for (std::size_t curr = 0; curr < multi_filtration_.size(); ++curr) { - if (_generator_can_be_added(multi_filtration_[curr], 0, end)) { - std::swap(multi_filtration_[end], multi_filtration_[curr]); - ++end; - } - } - - multi_filtration_.resize(end); - std::sort(multi_filtration_.begin(), multi_filtration_.end(), Is_strictly_smaller_lexicographically()); - } - - // FONCTIONNALITIES - - /** - * @brief Returns a generator with the minimal values of all parameters in any generator of the given filtration - * value. That is, the greatest lower bound of all generators. - */ - friend Generator factorize_below(const Multi_critical_filtration &f) { - if (f.num_generators() == 0) return Generator(); - Generator result(f.num_parameters(), Generator::T_inf); - for (const auto &g : f) { - if (g.is_nan() || g.is_minus_inf()) return g; - if (g.is_plus_inf()) continue; - for (std::size_t i = 0; i < f.num_parameters(); ++i) { - result[i] = std::min(result[i], g[i]); - } - } - return result; - } - - /** - * @brief Returns a generator with the maximal values of all parameters in any generator of the given filtration - * value. That is, the least upper bound of all generators. - */ - friend Generator factorize_above(const Multi_critical_filtration &f) { - if (f.num_generators() == 0) return Generator(); - Generator result(f.num_parameters(), -Generator::T_inf); - for (auto &g : f) { - if (g.is_nan() || g.is_plus_inf()) return g; - if (g.is_minus_inf()) continue; - for (std::size_t i = 0; i < g.num_parameters(); ++i) { - result[i] = std::max(result[i], g[i]); - } - } - return result; - } - - /** - * @brief Computes the smallest (resp. the greatest if `co` is true) scalar product of the all generators with the - * given vector. - * - * @tparam U Arithmetic type of the result. Default value: `T`. - * @param f Filtration value. - * @param x Vector of coefficients. - * @return Scalar product of @p f with @p x. - */ - template - friend U compute_linear_projection(const Multi_critical_filtration &f, const std::vector &x) { - if constexpr (co) { - U projection = std::numeric_limits::lowest(); - for (const auto &y : f) { - projection = std::max(projection, compute_linear_projection(y, x)); - } - return projection; - } else { - U projection = std::numeric_limits::max(); - for (const auto &y : f) { - projection = std::min(projection, compute_linear_projection(y, x)); - } - return projection; - } - } - - /** - * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries - * in the given filtration value. - * The grid has to be represented as a vector of vectors of ordered values convertible into `out_type`. An index - * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator. - * The inner vectors correspond to the possible values of the parameters, ordered by increasing value, - * forming therefore all together a 2D grid. - * - * @tparam out_type Signed arithmetic type. Default value: std::int32_t. - * @tparam U Type which is convertible into `out_type`. - * @param f Filtration value to project. - * @param grid Vector of vectors to project into. - * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is, - * the projection of \f$ f[i] \f$ is \f$ grid[i][out[i]] \f$ before simplification (if two generators were - * projected to the same point, the doubles are removed in the output). - */ - template - friend Multi_critical_filtration compute_coordinates_in_grid(Multi_critical_filtration f, - const std::vector> &grid) { - // TODO: by replicating the code of the 1-critical "project_onto_grid", this could be done with just one copy - // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really - // necessary in the use cases. To see later. - f.project_onto_grid(grid); - if constexpr (std::is_same_v) { - return f; - } else { - return f.as_type(); - } - } - - /** - * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration - * value. That is, if \f$ out \f$ is the result, \f$ out[i] = grid[i][f[i]] \f$. Assumes therefore, that the - * values stored in the filtration value corresponds to indices existing in the given grid. - * - * @tparam U Signed arithmetic type. - * @param f Filtration value storing coordinates compatible with `grid`. - * @param grid Vector of vector. - * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out[i] = grid[i][f[i]] \f$ before - * simplification (the output is simplified). - */ - template - friend Multi_critical_filtration evaluate_coordinates_in_grid(const Multi_critical_filtration &f, - const std::vector> &grid) { - Multi_critical_filtration out; - out.set_num_generators(f.num_generators()); - for (std::size_t i = 0; i < f.num_generators(); ++i) { - out[i] = evaluate_coordinates_in_grid(f[i], grid); - } - out.simplify(); - return out; - } - - // UTILITIES - - /** - * @brief Outstream operator. - */ - friend std::ostream &operator<<(std::ostream &stream, const Multi_critical_filtration &f) { - if (f.is_plus_inf()) { - stream << "[inf, ..., inf]"; - return stream; - } - if (f.is_minus_inf()) { - stream << "[-inf, ..., -inf]"; - return stream; - } - if (f.is_nan()) { - stream << "[NaN]"; - return stream; - } - stream << "(k=" << f.multi_filtration_.size() << ")["; - for (const auto &val : f) { - stream << val << "; "; - } - if (f.multi_filtration_.size() > 0) { - stream << "\b" - << "\b"; - } - stream << "]"; - return stream; - } - - friend bool unify_lifetimes(Multi_critical_filtration &f1, const Multi_critical_filtration &f2) { - bool modified = false; - for (const Generator &g : f2.multi_filtration_) { - modified |= f1.add_generator(g); - } - return modified; - } - - friend bool intersect_lifetimes(Multi_critical_filtration &f1, const Multi_critical_filtration &f2) { - if (f1.is_nan() || f2.is_nan()) return false; - - if constexpr (co) { - if (f1.is_plus_inf()) { - if (f2.is_plus_inf()) return false; - f1 = f2; - return true; - } - if (f1.is_minus_inf()) { - return false; - } - } else { - if (f1.is_minus_inf()) { - if (f2.is_minus_inf()) return false; - f1 = f2; - return true; - } - if (f1.is_plus_inf()) { - return false; - } - } - - Multi_critical_filtration res(1, -_get_default_value()); - // TODO: see if the order can be used to avoid n^2 complexity and - // perhaps even to replace add_generator by add_guaranteed_generator - for (const Generator &of1 : f1.multi_filtration_) { - for (const Generator &of2 : f2.multi_filtration_) { - // TODO: avoid one go-through by constructing nf directly as the max/min - Generator nf = of1; - if constexpr (co) { - nf.pull_to_greatest_common_lower_bound(of2); - } else { - nf.push_to_least_common_upper_bound(of2); - } - res.add_generator(nf); - } - } - std::swap(f1, res); - - return f1 != res; - } - - friend char *serialize_trivial(const Multi_critical_filtration &value, char *start) { - const auto nberOfGenerators = value.num_generators(); - const std::size_t type_size = sizeof(std::size_t); - memcpy(start, &nberOfGenerators, type_size); - char *curr = start + type_size; - for (const Generator &g : value) { - curr = serialize_trivial(g, curr); - } - return curr; - } - - friend const char *deserialize_trivial(Multi_critical_filtration &value, const char *start) { - const std::size_t type_size = sizeof(std::size_t); - std::size_t nberOfGenerators; - memcpy(&nberOfGenerators, start, type_size); - const char *curr = start + type_size; - value.set_num_generators(nberOfGenerators); - for (Generator &g : value) { - curr = deserialize_trivial(g, curr); - } - return curr; - } - - friend std::size_t get_serialization_size_of(const Multi_critical_filtration &value) { - std::size_t genSizes = sizeof(std::size_t); - for (const Generator &g : value) { - genSizes += get_serialization_size_of(g); - } - return genSizes; - } - - /** - * @brief Indicates if the class manages multi-critical filtration values. - */ - constexpr static const bool is_multi_critical = true; - - private: - Generators multi_filtration_; /**< Container for generators. */ - - struct Is_strictly_smaller_lexicographically { - // assumes both generators have the same length if not infinite/nan. - bool operator()(const Generator &g1, const Generator &g2) { - if (g1.is_nan() || g2.is_nan()) return !g1.is_nan(); - if (g1.is_plus_inf()) return false; - if (g2.is_plus_inf()) return true; - if (g1.is_minus_inf()) return false; - if (g2.is_minus_inf()) return true; - - // g1 and g2 have to finite and of the same size - for (std::size_t i = 0; i < g1.size(); ++i) { - if (g1[i] != g2[i]) return g1[i] < g2[i]; - } - return false; - } - }; - - constexpr static T _get_default_value() { return co ? Generator::T_inf : -Generator::T_inf; } - - constexpr static Generators _get_default_filtration_value() { return Generators{Generator{_get_default_value()}}; } - - /** - * @brief Verifies if @p b is strictly contained in the positive cone originating in `a`. - */ - static bool _strictly_contains(const Generator &a, const Generator &b) { - if constexpr (co) - return a > b; - else { - return a < b; - } - } - - /** - * @brief Verifies if @p b is contained in the positive cone originating in `a`. - */ - static bool _contains(const Generator &a, const Generator &b) { - if constexpr (co) - return a >= b; - else { - return a <= b; - } - } - - static bool _first_strictly_dominates(const Generator &a, const Generator &b) { - if constexpr (co) { - return !a.empty() && !b.empty() && a[0] < b[0]; - } else { - return !a.empty() && !b.empty() && a[0] > b[0]; - } - } - - static bool _first_dominates(const Generator &a, const Generator &b) { - if constexpr (co) { - return !a.empty() && !b.empty() && a[0] <= b[0]; - } else { - return !a.empty() && !b.empty() && a[0] >= b[0]; - } - } - - enum class Rel { EQUAL, DOMINATES, IS_DOMINATED, NONE }; - - static Rel _get_domination_relation(const Generator &a, const Generator &b) { - if (a.is_nan() || b.is_nan()) return Rel::NONE; - - GUDHI_CHECK(a.size() == b.size(), - "Two generators in the same k-critical value have to have the same numbers of parameters."); - - bool equal = true; - bool allGreater = true; - bool allSmaller = true; - for (unsigned int i = 0; i < a.size(); ++i) { - if (a[i] < b[i]) { - if (!allSmaller) return Rel::NONE; - equal = false; - allGreater = false; - } else if (a[i] > b[i]) { - if (!allGreater) return Rel::NONE; - equal = false; - allSmaller = false; - } - } - if (equal) return Rel::EQUAL; - - if constexpr (co) { - if (allSmaller) return Rel::DOMINATES; - return Rel::IS_DOMINATED; - } else { - if (allGreater) return Rel::DOMINATES; - return Rel::IS_DOMINATED; - } - } - - // assumes between 'curr' and 'end' everything is simplified: - // no nan values and if there is an inf/-inf, then 'end - curr == 1' - // modifies multi_filtration_ only if true is returned. - bool _generator_can_be_added(const Generator &x, std::size_t curr, std::size_t &end) { - if (x.empty() || x.is_nan()) return false; - - // assumes that everything between curr and end is simplified - // so, only multi_filtration_[curr] can be at inf or -inf. - if constexpr (co) { - if (multi_filtration_[curr].is_plus_inf() || (x.is_minus_inf() && end - curr != 0)) { - return false; - } - if (multi_filtration_[curr].is_minus_inf()) { - if (x.is_minus_inf()) { - return false; - } - end = curr; - return true; - } - if (x.is_plus_inf()) { - if (multi_filtration_[curr].is_plus_inf()) return false; - end = curr; - return true; - } - } else { - if (multi_filtration_[curr].is_minus_inf() || (x.is_plus_inf() && end - curr != 0)) { - return false; - } - if (multi_filtration_[curr].is_plus_inf()) { - if (x.is_plus_inf()) { - return false; - } - end = curr; - return true; - } - if (x.is_minus_inf()) { - if (multi_filtration_[curr].is_minus_inf()) return false; - end = curr; - return true; - } - } - - while (curr != end) { - Rel res = _get_domination_relation(multi_filtration_[curr], x); - if (res == Rel::IS_DOMINATED || res == Rel::EQUAL) return false; // x dominates or is equal - if (res == Rel::DOMINATES) { // x is dominated - --end; - std::swap(multi_filtration_[curr], multi_filtration_[end]); - } else { // no relation - ++curr; - } - } - return true; - } -}; - -}} // namespace Gudhi::multi_filtration - -namespace std { - -template -class numeric_limits> { - public: - static constexpr bool has_infinity = true; - - static constexpr Gudhi::multi_filtration::Multi_critical_filtration infinity() noexcept { - return Gudhi::multi_filtration::Multi_critical_filtration::inf(); - }; - - // non-standard - static constexpr Gudhi::multi_filtration::Multi_critical_filtration minus_infinity() noexcept { - return Gudhi::multi_filtration::Multi_critical_filtration::minus_inf(); - }; - - static constexpr Gudhi::multi_filtration::Multi_critical_filtration max() noexcept(false) { - throw std::logic_error( - "The maximal value cannot be represented with no finite numbers of generators." - "Use `max(number_of_generators, number_of_parameters)` instead"); - }; - - // non-standard, so I don't want to define default values. - static constexpr Gudhi::multi_filtration::Multi_critical_filtration max(unsigned int g, unsigned int n) noexcept { - std::vector::Generator> v( - g, std::vector(n, std::numeric_limits::max())); - return Gudhi::multi_filtration::Multi_critical_filtration(std::move(v)); - }; - - static constexpr Gudhi::multi_filtration::Multi_critical_filtration quiet_NaN() noexcept { - return Gudhi::multi_filtration::Multi_critical_filtration::nan(); - }; -}; - -} // namespace std - -#endif // MULTI_CRITICAL_FILTRATIONS_H_ diff --git a/multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h b/multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h new file mode 100644 index 00000000..be279faf --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_filtration/Multi_parameter_generator.h @@ -0,0 +1,1704 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, David Loiseaux + * + * Copyright (C) 2024-25 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @private + * @file Multi_parameter_generator.h + * @author Hannah Schreiber, David Loiseaux + * @brief Contains the @ref Gudhi::multi_filtration::Multi_parameter_generator class. + */ + +#ifndef MF_MULTI_PARAMETER_GENERATOR_H_ +#define MF_MULTI_PARAMETER_GENERATOR_H_ + +#include //std::lower_bound +#include //std::isnan, std::min +#include //std::size_t +#include //memcpy +#include //std::distance +#include //std::ostream +#include //std::numerical_limits +#include //std::logic_error +#include //std::is_arithmetic +#include //std::swap, std::move +#include +#include + +#include +#include +#include + +namespace Gudhi::multi_filtration { + +// declaration needed pre C++20 for friends with templates defined inside a class +template +U compute_euclidean_distance_to(); +template +U compute_norm(); + +/** + * private + * @class Multi_parameter_generator Multi_parameter_generator.h gudhi/Multi_parameter_generator.h + * @ingroup multi_filtration + * + * @brief Class encoding a single generator of @ref Dynamic_multi_parameter_filtration "". + * + * @tparam T Arithmetic type of an entry for one parameter of a filtration value. Has to be **signed** and + * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, + * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. + * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` + * can simply throw. Examples are the native types `double`, `float` and `int`. + */ +template +class Multi_parameter_generator +{ + public: + using Underlying_container = std::vector; /**< Underlying container for values. */ + + // CONSTRUCTORS + + /** + * @brief Default constructor. Builds generator with given number of parameters. + * All values are set at -inf. + * + * @param number_of_parameters If negative, takes the default value instead. Default value: 1. + */ + Multi_parameter_generator(int number_of_parameters = 1) + : generator_(number_of_parameters < 0 ? 1 : number_of_parameters, _get_default_value()) + {} + + /** + * @brief Builds generator with given number of parameters. All values are initialized at the given value. + * + * @param number_of_parameters If negative, is set to 1 instead. + * @param value Initialization value for every value in the generator. + */ + Multi_parameter_generator(int number_of_parameters, T value) + : generator_(number_of_parameters < 0 ? 1 : number_of_parameters, value) + {} + + /** + * @brief Builds generator that is initialized with the given range. The number of + * parameters are therefore deduced from the length of the range. + * + * @tparam ValueRange Range of types convertible to `T`. Should have a begin() and end() method. + * @param range Values of the generator. + */ + template , class = std::enable_if_t::has_begin> > + Multi_parameter_generator(const ValueRange &range) : generator_(range.begin(), range.end()) + {} + + /** + * @brief Builds generator that is initialized with the given range. The range is + * determined from the two given iterators. The number of parameters are therefore deduced from the distance + * between the two. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + */ + template + Multi_parameter_generator(Iterator it_begin, Iterator it_end) : generator_(it_begin, it_end) + {} + + /** + * @brief Builds generator with given number of parameters and values from the given range. + * The range is represented by @ref Multi_parameter_generator::Underlying_container "" and **moved** into the + * underlying container of the class. + * + * @param generators Values to move. + */ + Multi_parameter_generator(Underlying_container &&generators) : generator_(std::move(generators)) {} + + /** + * @brief Copy constructor. + */ + Multi_parameter_generator(const Multi_parameter_generator &other) = default; + + /** + * @brief Copy constructor. + * + * @tparam U Type convertible into `T`. + */ + template + Multi_parameter_generator(const Multi_parameter_generator &other) : generator_(other.begin(), other.end()) + {} + + /** + * @brief Move constructor. + */ + Multi_parameter_generator(Multi_parameter_generator &&other) noexcept = default; + + ~Multi_parameter_generator() = default; + + /** + * @brief Assign operator. + */ + Multi_parameter_generator &operator=(const Multi_parameter_generator &other) = default; + + /** + * @brief Move assign operator. + */ + Multi_parameter_generator &operator=(Multi_parameter_generator &&other) noexcept = default; + + /** + * @brief Assign operator. + * + * @tparam U Type convertible into `T`. + */ + template + Multi_parameter_generator &operator=(const Multi_parameter_generator &other) + { + generator_ = Underlying_container(other.begin(), other.end()); + return *this; + } + + /** + * @brief Swap operator. + */ + friend void swap(Multi_parameter_generator &g1, Multi_parameter_generator &g2) noexcept + { + g1.generator_.swap(g2.generator_); + } + + // VECTOR-LIKE + + using value_type = T; /**< Value type. */ + using size_type = typename Underlying_container::size_type; /**< Size type. */ + using difference_type = typename Underlying_container::difference_type; /**< Difference type. */ + using reference = value_type &; /**< Reference type. */ + using const_reference = const value_type &; /**< Const reference type. */ + using pointer = typename Underlying_container::pointer; /**< Pointer type. */ + using const_pointer = typename Underlying_container::const_pointer; /**< Const pointer type. */ + using iterator = typename Underlying_container::iterator; /**< Iterator type. */ + using const_iterator = typename Underlying_container::const_iterator; /**< Const iterator type. */ + using reverse_iterator = typename Underlying_container::reverse_iterator; /**< Reverse iterator type. */ + using const_reverse_iterator = typename Underlying_container::const_reverse_iterator; /**< Const reverse iterator. */ + + /** + * @brief Returns reference to the value of parameter `p`. + */ + reference operator[](size_type p) + { + GUDHI_CHECK(p < generator_.size(), std::out_of_range("Out of bound parameter.")); + return generator_[p]; + } + + /** + * @brief Returns const reference to the value of parameter `p`. + */ + const_reference operator[](size_type p) const + { + GUDHI_CHECK(p < generator_.size(), std::out_of_range("Out of bound parameter.")); + return generator_[p]; + } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. + */ + iterator begin() noexcept { return generator_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. + */ + const_iterator begin() const noexcept { return generator_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. + */ + const_iterator cbegin() const noexcept { return generator_.cbegin(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + iterator end() noexcept { return generator_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator end() const noexcept { return generator_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator cend() const noexcept { return generator_.cend(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + */ + reverse_iterator rbegin() noexcept { return generator_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + */ + const_reverse_iterator rbegin() const noexcept { return generator_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + */ + const_reverse_iterator crbegin() const noexcept { return generator_.crbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + reverse_iterator rend() noexcept { return generator_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator rend() const noexcept { return generator_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator crend() const noexcept { return generator_.crend(); } + + /** + * @brief Returns the size of the underlying container. Corresponds exactly to @ref num_parameters(), but enables to + * use the class as a classic range with a `begin`, `end` and `size` method. + */ + size_type size() const noexcept { return generator_.size(); } + + // CONVERTERS + + // like numpy + /** + * @brief Returns a copy with entries casted into the type given as template parameter. + * + * @tparam U New type for the entries. + * @return Copy with new entry type. + */ + template + Multi_parameter_generator as_type() const + { + std::vector out(generator_.begin(), generator_.end()); + return Multi_parameter_generator(std::move(out)); + } + + // ACCESS + + /** + * @brief Returns the number of parameters in the filtration value. + */ + size_type num_parameters() const { return generator_.size(); } + + /** + * @brief Returns a generator for which @ref is_plus_inf() returns `true`. + */ + static Multi_parameter_generator inf() { return Multi_parameter_generator(1, T_inf); } + + /** + * @brief Returns a generator for which @ref is_minus_inf() returns `true`. + */ + static Multi_parameter_generator minus_inf() { return Multi_parameter_generator(1, T_m_inf); } + + /** + * @brief Returns a generator for which @ref is_nan() returns `true`. + */ + static Multi_parameter_generator nan() + { + if constexpr (std::numeric_limits::has_quiet_NaN) { + return Multi_parameter_generator(1, std::numeric_limits::quiet_NaN()); + } else { + return Multi_parameter_generator(0); + } + } + + // DESCRIPTORS + + /** + * @brief Returns `true` if and only if the generator is considered as plus infinity. + */ + [[nodiscard]] bool is_plus_inf() const + { + for (const T &v : generator_) { + if (v != T_inf) return false; + } + return !generator_.empty(); + } + + /** + * @brief Returns `true` if and only if the generator is considered as minus infinity. + */ + [[nodiscard]] bool is_minus_inf() const + { + for (const T &v : generator_) { + if (v != T_m_inf) return false; + } + return !generator_.empty(); + } + + /** + * @brief Returns `true` if and only if the generator is considered as NaN. + */ + [[nodiscard]] bool is_nan() const + { + if constexpr (std::numeric_limits::has_quiet_NaN) { + for (const T &v : generator_) { + if (!std::isnan(v)) return false; + } + return true; + } else { + return generator_.empty(); + } + } + + /** + * @brief Returns `true` if and only if the generator is non-empty and is not considered as plus infinity, + * minus infinity or NaN. + */ + [[nodiscard]] bool is_finite() const + { + bool isInf = true, isMinusInf = true, isNan = true; + for (const auto &v : generator_) { + if (v != T_inf) isInf = false; + if (v != T_m_inf) isMinusInf = false; + if (!_is_nan(v)) isNan = false; + if (!isInf && !isMinusInf && !isNan) return true; + } + return false; + } + + // COMPARAISON OPERATORS + + /** + * @brief Returns `true` if and only if the positive cone generated by @p b is strictly contained in the + * cone generated by @p a. Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false + * does **not** imply \f$ a == b \f$. + */ + friend bool operator<(const Multi_parameter_generator &a, const Multi_parameter_generator &b) + { + if (&a == &b) return false; + if (a.is_nan() || b.is_nan()) return false; + if (a.is_minus_inf() || b.is_plus_inf()) return true; + if (b.is_minus_inf() || a.is_plus_inf()) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only generators with same number of parameters can be compared.")); + + bool isSame = true; + auto n = a.size(); + for (size_type i = 0U; i < n; ++i) { + if (a[i] > b[i]) return false; + if (isSame && a[i] != b[i]) isSame = false; + } + return !isSame; + } + + /** + * @brief Returns `true` if and only if the positive cone generated by @p a is strictly contained in the + * cone generated by @p b. Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return + * `false`. + */ + friend bool operator<=(const Multi_parameter_generator &a, const Multi_parameter_generator &b) + { + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + const bool aIsMinusInf = a.is_minus_inf(); + const bool aIsPlusInf = a.is_plus_inf(); + const bool bIsMinusInf = b.is_minus_inf(); + const bool bIsPlusInf = b.is_plus_inf(); + if ((aIsMinusInf && bIsMinusInf) || (aIsPlusInf && bIsPlusInf)) return true; + if (aIsMinusInf || bIsPlusInf) return true; + if (bIsMinusInf || aIsPlusInf) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only generators with same number of parameters can be compared.")); + + auto n = a.size(); + for (std::size_t i = 0U; i < n; ++i) { + if (a[i] > b[i]) return false; + } + return true; + } + + /** + * @brief Returns `true` if and only if the positive cone generated by @p b is contained in or are (partially) + * equal to the positive cone generated by @p a. + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false + * does **not** imply \f$ a == b \f$. + */ + friend bool operator>(const Multi_parameter_generator &a, const Multi_parameter_generator &b) { return b < a; } + + /** + * @brief Returns `true` if and only if the positive cone generated by @p a is contained in or are (partially) + * equal to the positive cone generated by @p b. + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return + * `false`. + */ + friend bool operator>=(const Multi_parameter_generator &a, const Multi_parameter_generator &b) { return b <= a; } + + /** + * @brief Returns `true` if and only if for each \f$ i,j \f$, \f$ a(i,j) \f$ is equal to \f$ b(i,j) \f$. + */ + friend bool operator==(const Multi_parameter_generator &a, const Multi_parameter_generator &b) + { + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + if (a.is_plus_inf()) { + return b.is_plus_inf(); + } + if (a.is_minus_inf()) { + return b.is_minus_inf(); + } + return a.generator_ == b.generator_; + } + + /** + * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. + */ + friend bool operator!=(const Multi_parameter_generator &a, const Multi_parameter_generator &b) { return !(a == b); } + + // ARITHMETIC OPERATORS + + // opposite + /** + * @brief Returns a generator such that an entry at index \f$ i \f$ is equal to \f$ -g(i) \f$. + * + * Used conventions: + * - \f$ -NaN = NaN \f$. + * + * @param g Value to opposite. + * @return The opposite of @p f. + */ + friend Multi_parameter_generator operator-(const Multi_parameter_generator &g) + { + using F = Multi_parameter_generator; + + std::vector result(g.generator_); + std::for_each(result.begin(), result.end(), [](T &v) { + if (v == F::T_inf) + v = F::T_m_inf; + else if (v == F::T_m_inf) + v = F::T_inf; + else + v = -v; + }); + return Multi_parameter_generator(std::move(result)); + } + + // subtraction + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Multi_parameter_generator operator-(Multi_parameter_generator g, const ValueRange &r) + { + g -= r; + return g; + } + + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) - g(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param r First element of the subtraction. + * @param g Second element of the subtraction. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_generator operator-(const ValueRange &r, const Multi_parameter_generator &g) + { + if (g.num_parameters() == 0) return g; + if (g.is_nan()) return nan(); + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return g -= r[0]; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) return nan(); + return g -= r[0][0]; + } else { + if (g.is_finite()) { + Multi_parameter_generator res = g; + res._apply_operation(r, [](T &valF, const T &valR) -> bool { + valF = -valF; + return _add(valF, valR); + }); + return res; + } + Multi_parameter_generator res(r.begin(), r.end()); + res._apply_operation(g[0], [](T &valRes, const T &valF) -> bool { return _subtract(valRes, valF); }); + return res; + } + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Multi_parameter_generator operator-(Multi_parameter_generator g, const T &val) + { + g -= val; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ val - g(p) \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the subtraction. + * @param g Second element of the subtraction. + */ + friend Multi_parameter_generator operator-(const T &val, Multi_parameter_generator g) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { + valF = -valF; + return _add(valF, valR); + }); + return g; + } + + /** + * @brief Modifies the first argument such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Multi_parameter_generator &operator-=(Multi_parameter_generator &g, const ValueRange &r) + { + if (g.num_parameters() == 0 || g.is_nan()) return g; + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return g -= r[0]; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) { + g = nan(); + return g; + } + return g -= r[0][0]; + } else { + if (!g.is_finite()) { + g.generator_.resize(r.size(), g[0]); + } + g._apply_operation(r, [](T &valF, const T &valR) -> bool { return _subtract(valF, valR); }); + } + + return g; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ p \f$ is equal to \f$ g(p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Multi_parameter_generator &operator-=(Multi_parameter_generator &g, const T &val) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { return _subtract(valF, valR); }); + return g; + } + + // addition + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Multi_parameter_generator operator+(Multi_parameter_generator g, const ValueRange &r) + { + g += r; + return g; + } + + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) + g(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param r First element of the addition. + * @param g Second element of the addition. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_generator operator+(const ValueRange &r, Multi_parameter_generator g) + { + g += r; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the addition. + * @param val Second element of the addition. + */ + friend Multi_parameter_generator operator+(Multi_parameter_generator g, const T &val) + { + g += val; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the addition. + * @param g Second element of the addition. + */ + friend Multi_parameter_generator operator+(const T &val, Multi_parameter_generator g) + { + g += val; + return g; + } + + /** + * @brief Modifies the first argument such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Multi_parameter_generator &operator+=(Multi_parameter_generator &g, const ValueRange &r) + { + if (g.num_parameters() == 0 || g.is_nan()) return g; + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return g += r[0]; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) { + g = nan(); + return g; + } + return g += r[0][0]; + } else { + if (!g.is_finite()) { + g.generator_.resize(r.size(), g[0]); + } + g._apply_operation(r, [](T &valF, const T &valR) -> bool { return _add(valF, valR); }); + } + + return g; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ p \f$ is equal to \f$ g(p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the addition. + * @param val Second element of the addition. + */ + friend Multi_parameter_generator &operator+=(Multi_parameter_generator &g, const T &val) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { return _add(valF, valR); }); + return g; + } + + // multiplication + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Multi_parameter_generator operator*(Multi_parameter_generator g, const ValueRange &r) + { + g *= r; + return g; + } + + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param r First element of the multiplication. + * @param g Second element of the multiplication. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_generator operator*(const ValueRange &r, Multi_parameter_generator g) + { + g *= r; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Multi_parameter_generator operator*(Multi_parameter_generator g, const T &val) + { + g *= val; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the multiplication. + * @param g Second element of the multiplication. + */ + friend Multi_parameter_generator operator*(const T &val, Multi_parameter_generator g) + { + g *= val; + return g; + } + + /** + * @brief Modifies the first argument such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Multi_parameter_generator &operator*=(Multi_parameter_generator &g, const ValueRange &r) + { + if (g.num_parameters() == 0 || g.is_nan()) return g; + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return g *= r[0]; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) { + g = nan(); + return g; + } + return g *= r[0][0]; + } else { + if (!g.is_finite()) { + g.generator_.resize(r.size(), g[0]); + } + g._apply_operation(r, [](T &valF, const T &valR) -> bool { return _multiply(valF, valR); }); + } + + return g; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ p \f$ is equal to \f$ g(p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Multi_parameter_generator &operator*=(Multi_parameter_generator &g, const T &val) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { return _multiply(valF, valR); }); + return g; + } + + // division + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Multi_parameter_generator operator/(Multi_parameter_generator g, const ValueRange &r) + { + g /= r; + return g; + } + + /** + * @brief Returns a generator such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) / g(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param r First element of the division. + * @param g Second element of the division. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_generator operator/(const ValueRange &r, const Multi_parameter_generator &g) + { + if (g.num_parameters() == 0) return g; + if (g.is_nan()) return nan(); + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return r[0] / g; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) return nan(); + return r[0][0] / g; + } else { + if (g.is_finite()) { + Multi_parameter_generator res = g; + res._apply_operation(r, [](T &valF, const T &valR) -> bool { + T tmp = valF; + valF = valR; + return _divide(valF, tmp); + }); + return res; + } + Multi_parameter_generator res(r.begin(), r.end()); + res._apply_operation(g[0], [](T &valRes, const T &valF) -> bool { return _divide(valRes, valF); }); + return res; + } + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ g(p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the division. + * @param val Second element of the division. + */ + friend Multi_parameter_generator operator/(Multi_parameter_generator g, const T &val) + { + g /= val; + return g; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ val / g(p) \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the division. + * @param g Second element of the division. + */ + friend Multi_parameter_generator operator/(const T &val, Multi_parameter_generator g) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { + T tmp = valF; + valF = valR; + return _divide(valF, tmp); + }); + return g; + } + + /** + * @brief Modifies the first argument such that an entry at index \f$ p \f$, with + * \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ g(p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ g(p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin(), end() and size() method. + * @param g First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Multi_parameter_generator &operator/=(Multi_parameter_generator &g, const ValueRange &r) + { + if (g.num_parameters() == 0 || g.is_nan()) return g; + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + if (r[0].is_finite()) { + return g /= r[0]; + } + if constexpr (!std::numeric_limits::has_quiet_NaN) + if (r[0].size() == 0) { + g = nan(); + return g; + } + return g /= r[0][0]; + } else { + if (!g.is_finite()) { + g.generator_.resize(r.size(), g[0]); + } + g._apply_operation(r, [](T &valF, const T &valR) -> bool { return _divide(valF, valR); }); + } + + return g; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ p \f$ is equal to \f$ g(p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param g First element of the division. + * @param val Second element of the division. + */ + friend Multi_parameter_generator &operator/=(Multi_parameter_generator &g, const T &val) + { + g._apply_operation(val, [](T &valF, const T &valR) -> bool { return _divide(valF, valR); }); + return g; + } + + // MODIFIERS + + /** + * @brief If the filtration value is at +/- infinity or NaN, the underlying container will probably only contain + * one element, representing the value. This method forces the underlying container to contain explicitly + * `number_of_parameters` elements. If the container is empty, it will fill all new elements with the default + * value -inf. If the container had more than one element, it does nothing and fails in Debug Mode if the number was + * different from `number_of_parameters`. + */ + void force_size_to_number_of_parameters(int number_of_parameters) + { + if (number_of_parameters < 1) return; + + if (generator_.size() > 1) { + GUDHI_CHECK(static_cast(number_of_parameters) == generator_.size(), + std::invalid_argument("Cannot force size to another number of parameters than set.")); + return; + } + + auto val = generator_.empty() ? _get_default_value() : generator_[0]; + generator_.resize(number_of_parameters, val); + } + + /** + * @brief Sets the generator to the least common upper bound between it and the given value. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x Range towards to push. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool push_to_least_common_upper_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + if (is_nan() || is_plus_inf()) return false; + if (x.size() == 0) { + *this = nan(); + return true; + } + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + return push_to_least_common_upper_bound(x[0], exclude_infinite_values); + } else { + if (is_minus_inf()) { + generator_ = Underlying_container(x.begin(), x.end()); + if (is_minus_inf()) { + *this = minus_inf(); + return false; + } + if (is_nan()) *this = nan(); + if (is_plus_inf()) *this = inf(); + return true; + } + + bool modified = false; + + auto it = x.begin(); + for (size_type p = 0; p < generator_.size(); ++p) { + T valX = *it; + if (!exclude_infinite_values || (valX != T_inf && valX != T_m_inf)) { + modified |= generator_[p] < valX; + generator_[p] = valX > generator_[p] ? valX : generator_[p]; + } + if (it + 1 != x.end()) ++it; + } + + return modified; + } + } + + /** + * @brief Sets each the generator to the greatest common lower bound between it and the given value. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x Range towards to pull. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool pull_to_greatest_common_lower_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + if (is_nan() || is_minus_inf()) return false; + if (x.size() == 0) { + *this = nan(); + return true; + } + + if constexpr (RangeTraits::is_dynamic_multi_filtration) { + return pull_to_greatest_common_lower_bound(x[0], exclude_infinite_values); + } else { + if (is_plus_inf()) { + generator_ = Underlying_container(x.begin(), x.end()); + if (is_plus_inf()) { + *this = inf(); + return false; + } + if (is_nan()) *this = nan(); + if (is_minus_inf()) *this = minus_inf(); + return true; + } + + bool modified = false; + + auto it = x.begin(); + for (size_type p = 0; p < generator_.size() && it != x.end(); ++p) { + T valX = *it; + if (!exclude_infinite_values || (valX != T_inf && valX != T_m_inf)) { + modified |= generator_[p] > valX; + generator_[p] = valX < generator_[p] ? valX : generator_[p]; + } + if (it + 1 != x.end()) ++it; + } + + return modified; + } + } + + /** + * @brief Projects the generator into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. + * + * @tparam OneDimArray A range of values convertible into `T` ordered by increasing value. Has to implement + * a begin, end and operator[] method. + * @param grid Vector of @p OneDimArray with size at least number of filtration parameters. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. + */ + template + void project_onto_grid(const std::vector &grid, bool coordinate = true) + { + GUDHI_CHECK( + grid.size() >= generator_.size(), + std::invalid_argument("The grid should not be smaller than the number of parameters in the filtration value.")); + + if (is_nan() || generator_.empty()) return; + + if (!is_finite()) generator_.resize(grid.size(), generator_[0]); + + for (size_type p = 0; p < generator_.size(); ++p) { + const auto &filtration = grid[p]; + auto d = std::distance( + filtration.begin(), + std::lower_bound( + filtration.begin(), filtration.end(), static_cast(generator_[p]))); + generator_[p] = coordinate ? static_cast(d) : static_cast(filtration[d]); + } + } + + // FONCTIONNALITIES + + /** + * @brief Computes the scalar product of the generator with the given vector. + * + * @tparam U Arithmetic type of the result. Default value: `T`. + * @param g Filtration value. + * @param x Vector of coefficients. + * @return Scalar product of @p f with @p x. + */ + template + friend U compute_linear_projection(const Multi_parameter_generator &g, const std::vector &x) + { + U projection = 0; + std::size_t size = std::min(x.size(), g.num_parameters()); + for (std::size_t i = 0; i < size; i++) projection += x[i] * static_cast(g[i]); + return projection; + } + + /** + * @brief Computes the euclidean distance from the first parameter to the second parameter. + * + * @param g Source filtration value. + * @param other Target filtration value. + * @return Euclidean distance between @p f and @p other. + */ + template + friend U compute_euclidean_distance_to(const Multi_parameter_generator &g, const Multi_parameter_generator &other) + { + if (!g.is_finite() || !other.is_finite()) { + if constexpr (std::numeric_limits::has_quiet_NaN) + return std::numeric_limits::quiet_NaN(); + else + return T_inf; + } + + GUDHI_CHECK(g.num_parameters() == other.num_parameters(), + std::invalid_argument("We cannot compute the distance between two points of different dimensions.")); + + if (g.num_parameters() == 1) return g[0] - other[0]; + + U out = 0; + for (size_type p = 0; p < g.num_parameters(); ++p) { + T v = g[p] - other[p]; + out += v * v; + } + if constexpr (std::is_integral_v) { + // to avoid Windows issue that don't know how to cast integers for cmath methods + return std::sqrt(static_cast(out)); + } else { + return std::sqrt(out); + } + } + + /** + * @brief Computes the sum of the squares of all values in the given generator. + * + * @param g Filtration value. + * @return The norm of @p f. + */ + template + friend U compute_squares(const Multi_parameter_generator &g) + { + U out = 0; + for (size_type p = 0; p < g.num_parameters(); ++p) { + out += g[p] * g[p]; + } + return out; + } + + /** + * @brief Computes the values in the given grid corresponding to the coordinates given by the given generator. + * That is, if \f$ out \f$ is the result, \f$ out(g,p) = grid[p][f(g,p)] \f$. Assumes therefore, that the + * values stored in the filtration value corresponds to indices existing in the given grid. + * + * @tparam U Signed arithmetic type. + * @param g Filtration value storing coordinates compatible with `grid`. + * @param grid Vector of vector. + * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out(g,p) = grid[p][f(g,p)] \f$. + */ + template + friend Multi_parameter_generator evaluate_coordinates_in_grid(const Multi_parameter_generator &g, + const std::vector > &grid) + { + GUDHI_CHECK(grid.size() >= g.num_parameters(), + std::invalid_argument( + "The size of the grid should correspond to the number of parameters in the filtration value.")); + + U grid_inf = Multi_parameter_generator::T_inf; + std::vector outVec(g.num_parameters()); + + for (size_type p = 0; p < g.num_parameters(); ++p) { + outVec[p] = (g[p] == T_inf ? grid_inf : grid[p][g[p]]); + } + + return Multi_parameter_generator(std::move(outVec)); + } + + // UTILITIES + + /** + * @brief Outstream operator. + */ + friend std::ostream &operator<<(std::ostream &stream, const Multi_parameter_generator &g) + { + if (g.is_plus_inf()) { + stream << "[inf, ..., inf]"; + return stream; + } + if (g.is_minus_inf()) { + stream << "[-inf, ..., -inf]"; + return stream; + } + if (g.is_nan()) { + stream << "[NaN]"; + return stream; + } + + const size_type num_param = g.num_parameters(); + + stream << "["; + for (size_type p = 0; p < num_param; ++p) { + stream << g[p]; + if (p < num_param - 1) stream << ", "; + } + stream << "]"; + + return stream; + } + + /** + * @brief Instream operator. + */ + friend std::istream &operator>>(std::istream &stream, Multi_parameter_generator &g) + { + char firstCharacter; + stream >> firstCharacter; + if (firstCharacter != '[') + throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + g.generator_.clear(); + auto pos = stream.tellg(); + stream >> firstCharacter; + if (firstCharacter == ']') return stream; + if (firstCharacter == 'i') { + while (firstCharacter != ']') { + stream >> firstCharacter; + } + g = Multi_parameter_generator::inf(); + return stream; + } + if (firstCharacter == '-') { + stream >> firstCharacter; + if (firstCharacter == 'i') { + while (firstCharacter != ']') { + stream >> firstCharacter; + } + g = Multi_parameter_generator::minus_inf(); + return stream; + } // else could be a negative number + } + if (firstCharacter == 'N') { + while (firstCharacter != ']') { + stream >> firstCharacter; + } + g = Multi_parameter_generator::nan(); + return stream; + } + + stream.seekg(pos, std::ios_base::beg); + char delimiter = '\0'; + while (delimiter != ']') { + g.generator_.push_back(_get_value(stream)); + if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_generator."); + stream >> delimiter; + } + + return stream; + } + + /** + * @brief Serialize given value into the buffer at given pointer. + * + * @param value Value to serialize. + * @param start Pointer to the start of the space in the buffer where to store the serialization. + * @return End position of the serialization in the buffer. + */ + friend char *serialize_value_to_char_buffer(const Multi_parameter_generator &value, char *start) + { + const size_type length = value.generator_.size(); + const std::size_t arg_size = sizeof(T) * length; + const std::size_t type_size = sizeof(size_type); + memcpy(start, &length, type_size); + memcpy(start + type_size, value.generator_.data(), arg_size); + return start + arg_size + type_size; + } + + /** + * @brief Deserialize the value from a buffer at given pointer and stores it in given value. + * + * @param value Value to fill with the deserialized filtration value. + * @param start Pointer to the start of the space in the buffer where the serialization is stored. + * @return End position of the serialization in the buffer. + */ + friend const char *deserialize_value_from_char_buffer(Multi_parameter_generator &value, const char *start) + { + const std::size_t type_size = sizeof(size_type); + size_type length; + memcpy(&length, start, type_size); + std::size_t arg_size = sizeof(T) * length; + value.generator_.resize(length); + memcpy(value.generator_.data(), start + type_size, arg_size); + return start + arg_size + type_size; + } + + /** + * @brief Returns the serialization size of the given filtration value. + */ + friend std::size_t get_serialization_size_of(const Multi_parameter_generator &value) + { + return sizeof(size_type) + (sizeof(T) * value.generator_.size()); + } + + /** + * @brief Plus infinity value of an entry of the filtration value. + */ + constexpr static const T T_inf = MF_T_inf; + + /** + * @brief Minus infinity value of an entry of the filtration value. + */ + constexpr static const T T_m_inf = MF_T_m_inf; + + private: + Underlying_container generator_; /**< Container of the filtration value elements. */ + + /** + * @brief Default value of an element in the filtration value. + */ + constexpr static T _get_default_value() { return T_m_inf; } + + /** + * @brief Applies operation on the elements of the filtration value. + */ + template + void _apply_operation(const ValueRange &range, F &&operate) + { + if (range.size() < generator_.size()) { + auto it = range.begin(); + for (size_type p = 0; p < generator_.size() && it != range.end(); ++p) { + std::forward(operate)(generator_[p], *it); + ++it; + } + } else { + bool allPlusInf = true; + bool allMinusInf = true; + bool allNaN = true; + + auto it = range.begin(); + for (size_type p = 0; p < generator_.size() && it != range.end(); ++p) { + bool isNotNaN = std::forward(operate)(generator_[p], *it); + if (generator_[p] != T_inf) allPlusInf = false; + if (generator_[p] != T_m_inf) allMinusInf = false; + if (isNotNaN) allNaN = false; + ++it; + } + + if (allPlusInf) + *this = inf(); + else if (allMinusInf) + *this = minus_inf(); + else if (allNaN) + *this = nan(); + } + } + + /** + * @brief Applies operation on the elements of the filtration value. + */ + template + void _apply_operation(const T &val, F &&operate) + { + bool allNaN = true; + + for (auto &p : generator_) { + if (std::forward(operate)(p, val)) allNaN = false; + } + + if (allNaN) *this = nan(); + } +}; + +} // namespace Gudhi::multi_filtration + +namespace std { + +template +class numeric_limits > +{ + public: + using Generator = Gudhi::multi_filtration::Multi_parameter_generator; + + static constexpr bool has_infinity = true; + static constexpr bool has_quiet_NaN = true; + + static constexpr Generator infinity() noexcept { return Generator::inf(); }; + + // non-standard + static constexpr Generator minus_infinity() noexcept { return Generator::minus_inf(); }; + + static constexpr Generator max() noexcept(false) + { + throw std::logic_error( + "The max value cannot be represented with no finite numbers of parameters." + "Use `max(number_of_parameters)` instead"); + }; + + static constexpr Generator max(std::size_t p) noexcept { return Generator(p, std::numeric_limits::max()); }; + + static constexpr Generator quiet_NaN() noexcept { return Generator::nan(); }; +}; + +} // namespace std + +#endif // MF_MULTI_PARAMETER_GENERATOR_H_ diff --git a/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h b/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h new file mode 100644 index 00000000..f3968f1a --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_conversions.h @@ -0,0 +1,237 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file multi_filtration_conversions.h + * @author Hannah Schreiber + */ + +#ifndef MF_CONVERSIONS_H_ +#define MF_CONVERSIONS_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Gudhi { + +namespace multi_filtration { + +/** + * @brief Converts the given multi filtration value into the type given as template argument. It is assumed that the + * given value is simplified (i.e. minimal and ordered lexicographically). If the new type is + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration it is additionally assumed that the given value is compatible + * with the type, that is,the number of parameters is 2 and the second parameter is an index (positive and convertible + * to an integer without loss). + * + * @tparam Out_multi_filtration New filtration value type. Has to be either + * @ref Gudhi::multi_filtration::Multi_parameter_filtration, + * @ref Gudhi::multi_filtration::Dynamic_multi_parameter_filtration or + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration with desired template arguments. + * @tparam T First template argument of the initial filtration value type. + * @tparam Co Second template argument of the initial filtration value type. + * @tparam Ensure1Criticality Third template argument of the initial filtration value type. + * @param f Filtration value to convert. + */ +template +Out_multi_filtration as_type(const Multi_parameter_filtration& f) +{ + using U = typename Out_multi_filtration::value_type; + constexpr bool co = Out_multi_filtration::has_negative_cones(); + constexpr bool one_crit = Out_multi_filtration::ensures_1_criticality(); + + if constexpr (std::is_same_v >) { + return f.template as_type(); + } else if constexpr (std::is_same_v >) { + return Out_multi_filtration(f.begin(), f.end(), f.num_parameters()); + } else if constexpr (std::is_same_v >) { + if (f.num_parameters() != 2) throw std::invalid_argument("Cannot convert a non-bifiltration to a bifiltration."); + U inf = co ? Out_multi_filtration::T_m_inf : Out_multi_filtration::T_inf; + + T maxIndex = 0; + for (std::size_t g = 0; g < f.num_generators(); ++g) { + maxIndex = maxIndex < f(g, 1) ? f(g, 1) : maxIndex; + } + + std::vector values(maxIndex + 1, inf); + for (std::size_t g = 0; g < f.num_generators(); ++g) { + values[f(g, 1)] = f(g, 0); + } + return Out_multi_filtration(std::move(values), 2); + } else { + throw std::invalid_argument("Given out multi filtration value is not available."); + } +} + +/** + * @brief Converts the given multi filtration value into the type given as template argument. It is assumed that the + * given value is simplified (i.e. minimal and ordered lexicographically). If the new type is + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration it is additionally assumed that the given value is compatible + * with the type, that is,the number of parameters is 2 and the second parameter is an index (positive and convertible + * to an integer without loss). + * + * @tparam Out_multi_filtration New filtration value type. Has to be either + * @ref Gudhi::multi_filtration::Multi_parameter_filtration, + * @ref Gudhi::multi_filtration::Dynamic_multi_parameter_filtration or + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration with desired template arguments. + * @tparam T First template argument of the initial filtration value type. + * @tparam Co Second template argument of the initial filtration value type. + * @tparam Ensure1Criticality Third template argument of the initial filtration value type. + * @param f Filtration value to convert. + */ +template +Out_multi_filtration as_type(const Dynamic_multi_parameter_filtration& f) +{ + using U = typename Out_multi_filtration::value_type; + constexpr bool co = Out_multi_filtration::has_negative_cones(); + constexpr bool one_crit = Out_multi_filtration::ensures_1_criticality(); + + if constexpr (std::is_same_v >) { + std::vector values(f.num_entries()); + std::size_t i = 0; + for (std::size_t g = 0; g < f.num_generators(); ++g) { + for (std::size_t p = 0; p < f.num_parameters(); ++p) { + values[i] = f(g, p); + ++i; + } + } + return Out_multi_filtration(std::move(values), f.num_parameters()); + } else if constexpr (std::is_same_v >) { + return f.template as_type(); + } else if constexpr (std::is_same_v >) { + if (f.num_parameters() != 2) throw std::invalid_argument("Cannot convert a non-bifiltration to a bifiltration."); + U inf = co ? Out_multi_filtration::T_m_inf : Out_multi_filtration::T_inf; + + T maxIndex = 0; + for (std::size_t g = 0; g < f.num_generators(); ++g) { + maxIndex = maxIndex < f(g, 1) ? f(g, 1) : maxIndex; + } + + std::vector values(maxIndex + 1, inf); + for (std::size_t g = 0; g < f.num_generators(); ++g) { + values[f(g, 1)] = f(g, 0); + } + return Out_multi_filtration(std::move(values), 2); + } else { + throw std::invalid_argument("Given out multi filtration value is not available."); + } +} + +/** + * @brief Converts the given multi filtration value into the type given as template argument. + * + * @tparam Out_multi_filtration New filtration value type. Has to be either + * @ref Gudhi::multi_filtration::Multi_parameter_filtration, + * @ref Gudhi::multi_filtration::Dynamic_multi_parameter_filtration or + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration with desired template arguments. + * @tparam T First template argument of the initial filtration value type. + * @tparam Co Second template argument of the initial filtration value type. + * @tparam Ensure1Criticality Third template argument of the initial filtration value type. + * @param f Filtration value to convert. + */ +template +Out_multi_filtration as_type(const Degree_rips_bifiltration& f) +{ + using U = typename Out_multi_filtration::value_type; + constexpr bool co = Out_multi_filtration::has_negative_cones(); + constexpr bool one_crit = Out_multi_filtration::ensures_1_criticality(); + + if constexpr (std::is_same_v >) { + return f.template as_type(); + } else { + auto gen_index = [&f](std::size_t i) { + if constexpr (Out_multi_filtration::has_negative_cones()) { + return f.num_generators() - 1 - i; + } else { + return i; + } + }; + + auto strictly_dominates = [](T a, T b) { + if constexpr (Out_multi_filtration::has_negative_cones()) { + return a < b; + } else { + return a > b; + } + }; + + if (f.size() == 0) return Out_multi_filtration(0); + + std::vector order(f.num_generators()); + std::iota(order.begin(), order.end(), 0); + // lexicographical order + std::sort(order.begin(), order.end(), [&](std::size_t i, std::size_t j){ + if (f(i, 0) == f(j, 0)) return f(i, 1) < f(j, 1); // f(i, 1) and f(j, 1) cannot be equal for i != j + return f(i, 0) < f(j, 0); + }); + + if constexpr (std::is_same_v >) { + std::vector values; + values.reserve(f.num_generators() * 2); + std::size_t g = order[gen_index(0)]; + T threshold = g; + values.push_back(f(g, 0)); + values.push_back(threshold); + for (std::size_t i = 1; i < f.num_generators(); ++i) { + g = order[gen_index(i)]; + if (strictly_dominates(threshold, g)) { + threshold = g; + values.push_back(f(g, 0)); + values.push_back(threshold); + } + } + if constexpr (co) { + // lexicographical order + const std::size_t max_idx = values.size() - 1; + for (std::size_t i = 0; i < values.size() / 2; i += 2) { + std::swap(values[i], values[max_idx - 1 - i]); + std::swap(values[i + 1], values[max_idx - i]); + } + } + + return Out_multi_filtration(std::move(values), 2); + } else if constexpr (std::is_same_v >) { + std::vector > values; + values.reserve(f.num_generators()); + std::size_t g = order[gen_index(0)]; + T threshold = g; + values.emplace_back(std::vector{static_cast(f(g, 0)), threshold}); + for (std::size_t i = 1; i < f.num_generators(); ++i) { + g = order[gen_index(i)]; + if (strictly_dominates(threshold, g)) { + threshold = g; + std::vector v = {static_cast(f(g, 0)), threshold}; + values.emplace_back(std::move(v)); + } + } + if constexpr (co) { + // lexicographical order + std::reverse(values.begin(), values.end()); + } + + return Out_multi_filtration(std::move(values), 2); + } else { + throw std::invalid_argument("Given out multi filtration value is not available."); + } + } +} + +} // namespace multi_filtration + +} // namespace Gudhi + +#endif // MF_CONVERSIONS_H_ diff --git a/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h b/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h new file mode 100644 index 00000000..2b8af1c5 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_filtration/multi_filtration_utils.h @@ -0,0 +1,225 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @private + * @file multi_filtration_utils.h + * @author Hannah Schreiber + */ + +#ifndef MF_UTILS_H_ +#define MF_UTILS_H_ + +#include +#include +#include +#include +#include +#include + +namespace Gudhi { + +namespace multi_filtration { + +/** + * @private + */ +template +class RangeTraits +{ + private: + static auto check_begin(...) -> std::false_type; + template + static auto check_begin(U x) -> decltype(x.begin(), std::true_type{}); + + static auto check_dynamic_filtration(...) -> std::false_type; + template + static auto check_dynamic_filtration(U x) -> decltype(x.operator[](std::size_t{}), std::true_type{}); + + static auto check_filtration(...) -> std::false_type; + template + static auto check_filtration(U x) -> decltype(x.ensures_1_criticality(), std::true_type{}); + + public: + static constexpr bool has_begin = decltype(check_begin(std::declval()))::value; + static constexpr bool is_multi_filtration = decltype(check_filtration(std::declval()))::value; + static constexpr bool is_dynamic_multi_filtration = + is_multi_filtration && decltype(check_dynamic_filtration(std::declval()))::value; +}; + +/** + * @private + */ +template +constexpr bool _is_nan(T val) +{ + if constexpr (std::is_integral_v) { + // to avoid Windows issue which don't know how to cast integers for cmath methods + return false; + } else { + return std::isnan(val); + } +}; + +/** + * @private + * @brief Infinity value of an entry of the filtration value. + */ +template +constexpr const T MF_T_inf = + std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); + +/** + * @private + * @brief Minus infinity value of an entry of the filtration value. + */ +template +constexpr const T MF_T_m_inf = + std::numeric_limits::has_infinity ? -std::numeric_limits::infinity() : std::numeric_limits::lowest(); + +/** + * @private + * @brief Adds v1 and v2, stores the result in v1 and returns true if and only if v1 was modified. + */ +template +constexpr bool _add(T &v1, T v2) +{ + if (_is_nan(v1) || _is_nan(v2) || (v1 == MF_T_inf && v2 == MF_T_m_inf) || + (v1 == MF_T_m_inf && v2 == MF_T_inf)) { + v1 = std::numeric_limits::quiet_NaN(); + return false; + } + if (v1 == MF_T_inf || v1 == MF_T_m_inf) { + return true; + } + if (v2 == MF_T_inf || v2 == MF_T_m_inf) { + v1 = v2; + return true; + } + + v1 += v2; + return true; +}; + +/** + * @private + * @brief Subtracts v1 and v2, stores the result in v1 and returns true if and only if v1 was modified. + */ +template +constexpr bool _subtract(T &v1, T v2) +{ + return _add(v1, v2 == MF_T_inf ? MF_T_m_inf : (v2 == MF_T_m_inf ? MF_T_inf : -v2)); +}; + +/** + * @private + * @brief Multiplies v1 and v2, stores the result in v1 and returns true if and only if v1 was modified. + */ +template +constexpr bool _multiply(T &v1, T v2) +{ + bool v1_is_infinite = v1 == MF_T_inf || v1 == MF_T_m_inf; + bool v2_is_infinite = v2 == MF_T_inf || v2 == MF_T_m_inf; + + if (_is_nan(v1) || _is_nan(v2) || (v1_is_infinite && v2 == 0) || (v1 == 0 && v2_is_infinite)) { + v1 = std::numeric_limits::quiet_NaN(); + return false; + } + + if ((v1 == MF_T_inf && v2 > 0) || (v1 == MF_T_m_inf && v2 < 0) || (v1 < 0 && v2 == MF_T_m_inf) || + (v1 > 0 && v2 == MF_T_inf)) { + v1 = MF_T_inf; + return true; + } + + if ((v1 == MF_T_inf && v2 < 0) || (v1 == MF_T_m_inf && v2 > 0) || (v1 > 0 && v2 == MF_T_m_inf) || + (v1 < 0 && v2 == MF_T_inf)) { + v1 = MF_T_m_inf; + return true; + } + + v1 *= v2; + return true; +}; + +/** + * @private + * @brief Divides v1 and v2, stores the result in v1 and returns true if and only if v1 was modified. + */ +template +constexpr bool _divide(T &v1, T v2) +{ + bool v1_is_infinite = v1 == MF_T_inf || v1 == MF_T_m_inf; + bool v2_is_infinite = v2 == MF_T_inf || v2 == MF_T_m_inf; + + if (_is_nan(v1) || _is_nan(v2) || v2 == 0 || (v1_is_infinite && v2_is_infinite)) { + v1 = std::numeric_limits::quiet_NaN(); + return false; + } + + if (v1 == 0 || (v1_is_infinite && v2 > 0)) return true; + + if (v1_is_infinite && v2 < 0) { + v1 = v1 == MF_T_inf ? MF_T_m_inf : (v1 == MF_T_m_inf ? MF_T_inf : -v1); + return true; + } + + if (v2_is_infinite) { + v1 = 0; + return true; + } + + v1 /= v2; + return true; +}; + +template +T _get_value(std::istream &stream) +{ + if constexpr (std::numeric_limits::has_infinity) { + auto pos = stream.tellg(); + char first; + stream >> first; + if (first == 'i') { + stream >> first; // n + stream >> first; // f + return std::numeric_limits::infinity(); + } + if (first == '-') { + stream >> first; // i + if (first == 'i') { + stream >> first; // n + stream >> first; // f + return -std::numeric_limits::infinity(); + } // else could be a negative number + } + if (first == 'n') { + if constexpr (std::numeric_limits::has_quiet_NaN) { + stream >> first; // a + stream >> first; // n + return std::numeric_limits::quiet_NaN(); + } else { + throw std::invalid_argument("Wrong input stream format for value, no nan values allowed."); + } + } + stream.seekg(pos, std::ios_base::beg); + } + + T val; + stream >> val; + if (stream.fail()) throw std::invalid_argument("Wrong input stream format for value."); + return val; +}; + +} // namespace multi_filtration + +} // namespace Gudhi + +#endif // MF_UTILS_H_ diff --git a/multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h b/multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h new file mode 100644 index 00000000..ed7df0c8 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_parameter_filtered_complex.h @@ -0,0 +1,415 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - 2025/04 Hannah Schreiber: Reorganization + documentation. + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_parameter_filtered_complex.h + * @author David Loiseaux + * @brief Contains the @ref Gudhi::multi_persistence::Multi_parameter_filtered_complex class. + */ + +#ifndef MP_FILTERED_COMPLEX_H_INCLUDED +#define MP_FILTERED_COMPLEX_H_INCLUDED + +#include //std::uint32_t +#include +#include +#include +#include +#include +#include +#include + +#include +#include //for lex order + +namespace Gudhi { +namespace multi_persistence { + +// TODO: better name +/** + * @class Multi_parameter_filtered_complex Multi_parameter_filtered_complex.h gudhi/Multi_parameter_filtered_complex.h + * @ingroup multi_persistence + * + * @brief Class storing the boundaries, the dimensions and the filtration values of all cells composing a complex. + * + * @tparam MultiFiltrationValue Filtration value class respecting the @ref MultiFiltrationValue concept. + */ +template +class Multi_parameter_filtered_complex +{ + public: + using Index = std::uint32_t; /**< Complex index type. */ + using Filtration_value = MultiFiltrationValue; /**< Filtration value type. */ + using T = typename Filtration_value::value_type; /**< Numerical type of an element in a filtration value. */ + using Filtration_value_container = std::vector; /**< Filtration value container type. */ + using Boundary = std::vector; /**< Cell boundary type, represented by the complex indices of its faces. */ + using Boundary_container = std::vector; /**< Boundary container type. */ + using Dimension = int; /**< Dimension type. */ + using Dimension_container = std::vector; /**< Dimension container type. */ + + /** + * @brief Default constructor. Constructs an empty complex. + */ + Multi_parameter_filtered_complex() : filtrationValues_(), maxDimension_(-1), isOrderedByDimension_(true) {} + + /** + * @brief Constructs the complex by copying all three given containers into the class. + * + * @param boundaries Container of boundaries. A boundary has to be described by the indices of its faces in this + * container. E.g., if a vertex \f$ v \f$ is stored at index \f$ i \f$ and another vertex at index \f$ j \f$, then + * `boundaries[i]` and `boundaries[j]` are both empty and if the edge \f$ (v,u) \f$ is at index \f$ k \f$, then + * `boundaries[k]` is equal to `{i, j}`. All boundaries are expected to be ordered by increasing index value. + * @param dimensions Dimension container. The value at index \f$ i \f$ has to correspond to the dimension of the + * cell at index \f$ i \f$ in `boundaries`. + * @param filtrationValues Filtration value container. The value at index \f$ i \f$ has to correspond to the + * filtration value of the cell at index \f$ i \f$ in `boundaries`. + */ + Multi_parameter_filtered_complex(const Boundary_container& boundaries, + const Dimension_container& dimensions, + const Filtration_value_container& filtrationValues) + : boundaries_(boundaries), + dimensions_(dimensions), + filtrationValues_(filtrationValues), + maxDimension_(-1), + isOrderedByDimension_(false) + { + _initialize_dimension_utils(); + } + + /** + * @brief Constructs the complex by moving all three given containers to the class. + * + * @param boundaries Container of boundaries. A boundary has to be described by the indices of its faces in this + * container. E.g., if a vertex \f$ v \f$ is stored at index \f$ i \f$ and another vertex at index \f$ j \f$, then + * `boundaries[i]` and `boundaries[j]` are both empty and if the edge \f$ (v,u) \f$ is at index \f$ k \f$, then + * `boundaries[k]` is equal to `{i, j}`. All boundaries are expected to be ordered by increasing index value. + * @param dimensions Dimension container. The value at index \f$ i \f$ has to correspond to the dimension of the + * cell at index \f$ i \f$ in `boundaries`. + * @param filtrationValues Filtration value container. The value at index \f$ i \f$ has to correspond to the + * filtration value of the cell at index \f$ i \f$ in `boundaries`. + */ + Multi_parameter_filtered_complex(Boundary_container&& boundaries, + Dimension_container&& dimensions, + Filtration_value_container&& filtrationValues) + : boundaries_(std::move(boundaries)), + dimensions_(std::move(dimensions)), + filtrationValues_(std::move(filtrationValues)), + maxDimension_(0), + isOrderedByDimension_(false) + { + _initialize_dimension_utils(); + } + + /** + * @brief Returns the number of cells in the complex. + */ + [[nodiscard]] Index get_number_of_cycle_generators() const { return boundaries_.size(); } + + /** + * @brief Returns the number of parameters in the filtration. + */ + [[nodiscard]] Index get_number_of_parameters() const + { + if (filtrationValues_.empty()) return 0; + return filtrationValues_[0].num_parameters(); + } + + /** + * @brief Returns true if and only if the boundaries are ordered by dimension. That is, if an index increases, + * the represented cell at the new index can only have same or higher dimension than the cell at the index before. + */ + [[nodiscard]] bool is_ordered_by_dimension() const { return isOrderedByDimension_; } + + /** + * @brief Returns a const reference to the filtration value container. + */ + const Filtration_value_container& get_filtration_values() const { return filtrationValues_; } + + /** + * @brief Returns a reference to the filtration value container. + * @warning The container is not const such that the user can easily modify/update a filtration value. But do not + * modify the size of the container, its indices have still to correspond to the indices in the other containers. + */ + Filtration_value_container& get_filtration_values() { return filtrationValues_; } + + /** + * @brief Returns a const reference to the dimension container. + */ + [[nodiscard]] const Dimension_container& get_dimensions() const { return dimensions_; } + + /** + * @brief Returns a const reference to the boundary container. + */ + [[nodiscard]] const Boundary_container& get_boundaries() const { return boundaries_; } + + /** + * @brief Returns the maximal dimension of a cell in the complex. + */ + [[nodiscard]] Dimension get_max_dimension() const { return maxDimension_; } + + /** + * @brief Sorts the container internally such that the cells are ordered first by dimension and then + * co-lexicographically by filtration values. If two cells have same dimension and same filtration value, they are + * considered equal (i.e., they relative position from each other does not matter). + * Note that the indices of the cells changes therefore. + */ + void sort_by_dimension_co_lexicographically() + { + using namespace Gudhi::multi_filtration; + + sort([&](Index i, Index j) -> bool { + if (dimensions_[i] == dimensions_[j]) { + return is_strict_less_than_lexicographically(filtrationValues_[i], filtrationValues_[j]); + } + return dimensions_[i] < dimensions_[j]; + }); + } + + /** + * @brief Sorts the internal containers using the given comparaison method. + * Note that the indices of the cells changes therefore. + * + * @tparam Comp Method type with signature (Index, Index)->bool. + * @param comparaison Method taking two complex indices (those before the sort) as input and returns true if and + * only if the cell at the first index is supposed to be placed before the cell at the second index. + */ + template + void sort(Comp&& comparaison) + { + // TODO: test if it is not faster to just reconstruct everything instead of swapping + // Note: perm and inv have to be build in any case + // if we reconstruct, we additionally build three containers of vector of Index, of Index + // and of Filtration_value, which will be swapped respectively with boundaries_, dimensions_ + // and filtrationValues_ + // in this version (swapping), we additionally build two containers of Index instead + // so should theoretically be better, but not so sure if we replace the containers with + // completely flat containers one day, i.e. with no cheap swap method + std::vector perm(boundaries_.size()); + std::iota(perm.begin(), perm.end(), 0); + std::vector pos = perm; + std::vector invPos = perm; + std::sort(perm.begin(), perm.end(), std::forward(comparaison)); + std::vector invPerm(boundaries_.size()); + for (Index i = 0; i < perm.size(); ++i) invPerm[perm[i]] = i; + + Dimension lastDim = -1; + isOrderedByDimension_ = true; + + for (Index curr = 0; curr < perm.size(); ++curr) { + Index p = perm[curr]; + Index i = pos[p]; + if (i != curr) { + GUDHI_CHECK(curr < i, std::runtime_error("Got curr " + std::to_string(curr) + " >= i " + std::to_string(i))); + std::swap(boundaries_[curr], boundaries_[i]); + std::swap(dimensions_[curr], dimensions_[i]); + swap(filtrationValues_[curr], filtrationValues_[i]); + std::swap(pos[invPos[curr]], pos[p]); + std::swap(invPos[curr], invPos[pos[invPos[curr]]]); + } + for (Index& b : boundaries_[curr]) b = invPerm[b]; + std::sort(boundaries_[curr].begin(), boundaries_[curr].end()); + if (lastDim > dimensions_[curr]) isOrderedByDimension_ = false; + lastDim = dimensions_[curr]; + } + } + + /** + * @brief Removes completely from the complex all cells of dimension strictly higher than given. + * + * @warning If @ref is_ordered_by_dimension does not return true, the complex is sorted by dimension before pruning. + * So, the indexing changes afterwards. + * + * @param maxDim Maximal dimension to keep. + * @return Number of remaining cells in the complex. + */ + Index prune_above_dimension(int maxDim) + { + if (!isOrderedByDimension_) sort_by_dimension_co_lexicographically(); + Index i = 0; + while (i < dimensions_.size() && dimensions_[i] < maxDim + 1) ++i; + boundaries_.resize(i); + dimensions_.resize(i); + filtrationValues_.resize(i); + maxDimension_ = dimensions_.empty() ? -1 : dimensions_.back(); + return i; + } + + /** + * @brief Projects all filtration values into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * An index \f$ i \f$ of the grid corresponds to the same parameter as the index \f$ i \f$ in a generator of the + * filtration value. The internal vectors correspond to the possible values of the parameters, ordered by increasing + * value, forming therefore all together a 2D grid. + * + * @param grid Vector of vector with size at least number of filtration parameters. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. + */ + void coarsen_on_grid(const std::vector >& grid, bool coordinate = true) + { + for (auto gen = 0U; gen < filtrationValues_.size(); ++gen) { + filtrationValues_[gen].project_onto_grid(grid, coordinate); + } + } + + /** + * @brief Builds a new complex by reordering the cells in the given complex with the given permutation map. + */ + friend Multi_parameter_filtered_complex build_permuted_complex(const Multi_parameter_filtered_complex& complex, + const std::vector& permutation) { + if (permutation.size() > complex.get_number_of_cycle_generators()) + throw std::invalid_argument("Invalid permutation size. Got perm size: " + std::to_string(permutation.size()) + + " while complex size: " + std::to_string(complex.get_number_of_cycle_generators())); + + const Index flag = -1; + std::vector inv(complex.get_number_of_cycle_generators(), flag); + for (Index i = 0; i < permutation.size(); ++i) inv[permutation[i]] = i; + + Boundary_container newGenerators(permutation.size()); + Dimension_container newGeneratorDimensions(permutation.size()); + Filtration_value_container newFiltrationValues(permutation.size()); + + for (Index i = 0; i < permutation.size(); ++i) { + const auto& boundary = complex.boundaries_[permutation[i]]; + newGenerators[i].reserve(boundary.size()); + for (Index b : boundary) { + if (inv[b] != flag) newGenerators[i].push_back(inv[b]); + } + std::sort(newGenerators[i].begin(), newGenerators[i].end()); + newGeneratorDimensions[i] = complex.dimensions_[permutation[i]]; + newFiltrationValues[i] = complex.filtrationValues_[permutation[i]]; + } + + return Multi_parameter_filtered_complex( + std::move(newGenerators), std::move(newGeneratorDimensions), std::move(newFiltrationValues)); + } + + /** + * @brief Builds a new complex by reordering the cells in the given complex the same way than + * @ref sort_by_dimension_co_lexicographically. Returns a pair with the new complex as first element and the + * permutation map used as second element. + */ + friend std::pair > build_permuted_complex( + const Multi_parameter_filtered_complex& complex) + { + using namespace Gudhi::multi_filtration; + + std::vector perm(complex.get_number_of_cycle_generators()); + std::iota(perm.begin(), perm.end(), 0); + std::sort(perm.begin(), perm.end(), [&](Index i, Index j) -> bool { + if (complex.dimensions_[i] == complex.dimensions_[j]) { + return is_strict_less_than_lexicographically(complex.filtrationValues_[i], complex.filtrationValues_[j]); + } + return complex.dimensions_[i] < complex.dimensions_[j]; + }); + auto out = build_permuted_complex(complex, perm); + return std::make_pair(std::move(out), std::move(perm)); + } + + /** + * @brief Builds a new complex from the given one by projecting its filtration values on a grid. + * See @ref coarsen_on_grid with the paramater `coordinate` at true. + */ + friend auto build_complex_coarsen_on_grid(const Multi_parameter_filtered_complex& complex, + const std::vector >& grid) + { + using namespace Gudhi::multi_filtration; + using Return_filtration_value = decltype(std::declval().template as_type()); + using Return_complex = Multi_parameter_filtered_complex; + + typename Return_complex::Filtration_value_container coords(complex.get_number_of_cycle_generators()); + for (Index gen = 0U; gen < coords.size(); ++gen) { + coords[gen] = compute_coordinates_in_grid(complex.filtrationValues_[gen], grid); + } + return Return_complex(complex.boundaries_, complex.dimensions_, coords); + } + + // /** + // * @brief Compares two boundaries and returns true if and only if the size of the first is strictly smaller than + // the + // * second, or, if the two sizes are the same, the first is lexicographically strictly smaller than the second. + // * The boundaries are assumed to be ordered by increasing values. + // */ + // static bool boundary_is_strictly_smaller_than(const Boundary& b1, const Boundary& b2) { + // // we want faces to be smaller than proper cofaces + // if (b1.size() < b2.size()) return true; + // if (b1.size() > b2.size()) return false; + + // // lexico for others + // for (Index i = 0; i < b2.size(); ++i){ + // if (b1[i] < b2[i]) return true; + // if (b1[i] > b2[i]) return false; + // } + + // // equal + // return false; + // } + + /** + * @brief Outstream operator. + */ + friend std::ostream& operator<<(std::ostream& stream, const Multi_parameter_filtered_complex& complex) + { + stream << "Boundary:\n"; + stream << "{\n"; + for (Index i = 0; i < complex.boundaries_.size(); ++i) { + const auto& boundary = complex.boundaries_[i]; + stream << i << ": {"; + for (auto b : boundary) stream << b << ", "; + if (!boundary.empty()) stream << "\b" << "\b "; + stream << "},\n"; + } + stream << "}\n"; + + stream << "Dimensions: (max " << complex.get_max_dimension() << ")\n"; + stream << "{"; + for (auto d : complex.dimensions_) stream << d << ", "; + if (!complex.dimensions_.empty()) { + stream << "\b" << "\b"; + } + stream << "}\n"; + + stream << "Filtration values:\n"; + stream << "{\n"; + for (auto f : complex.filtrationValues_) stream << f << "\n"; + stream << "}\n"; + + return stream; + } + + private: + Boundary_container boundaries_; /**< Boundary container. */ + Dimension_container dimensions_; /**< Dimension container. */ + Filtration_value_container filtrationValues_; /**< Filtration value container. */ + Dimension maxDimension_; /**< Maximal dimension of a cell. */ + bool isOrderedByDimension_; /**< True if and only if the containers are ordered by dimension. */ + + /** + * @brief Initializes maxDimension_ and isOrderedByDimension_ + */ + void _initialize_dimension_utils() + { + isOrderedByDimension_ = true; + if (dimensions_.empty()) return; + + for (Index i = 0; i < dimensions_.size() - 1; ++i) { + maxDimension_ = std::max(dimensions_[i], maxDimension_); + if (dimensions_[i] > dimensions_[i + 1]) isOrderedByDimension_ = false; + } + maxDimension_ = std::max(dimensions_.back(), maxDimension_); + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_FILTERED_COMPLEX_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_parameter_filtration.h b/multipers/gudhi/gudhi/Multi_parameter_filtration.h new file mode 100644 index 00000000..b1c1b053 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_parameter_filtration.h @@ -0,0 +1,2628 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, David Loiseaux + * + * Copyright (C) 2024-25 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_parameter_filtration.h + * @author Hannah Schreiber, David Loiseaux + * @brief Contains the @ref Gudhi::multi_filtration::Multi_parameter_filtration class. + */ + +#ifndef MF_MULTI_PARAMETER_FILTRATION_H_ +#define MF_MULTI_PARAMETER_FILTRATION_H_ + +#include //std::lower_bound +#include //std::isnan, std::min +#include //std::size_t +#include //std::int32_t, std::uint8_t +#include //memcpy +#include //std::distance +#include //std::ostream +#include //std::numerical_limits +#include //std::logic_error +#include //std::is_arithmetic +#include //std::swap, std::move +#include //std::iota +#include +#include + +#include +#include +#include + +namespace Gudhi::multi_filtration { + +// declaration needed pre C++20 for friends with templates defined inside a class +template +U compute_linear_projection(); +template +U compute_euclidean_distance_to(); +template +U compute_norm(); +template +void compute_coordinates_in_grid(); +template +void evaluate_coordinates_in_grid(); +template +bool is_strict_less_than_lexicographically(); +template +bool is_less_or_equal_than_lexicographically(); + +/** + * @class Multi_parameter_filtration Multi_parameter_filtration.h gudhi/Multi_parameter_filtration.h + * @ingroup multi_filtration + * + * @brief Class encoding the different generators, i.e., apparition times, of a \f$ k \f$-critical + * \f$\mathbb R^n\f$-filtration value. E.g., the filtration value of a simplex, or, of the algebraic generator of a + * module presentation. The encoding is compacted into a single vector, so if a lot of non trivial modifications are + * done (that not only consists of simply adding new generators at the end of the vector), it is probably preferable + * to use @ref Dynamic_multi_parameter_filtration instead. Implements the concept @ref FiltrationValue of the + * @ref Gudhi::Simplex_tree and the concept @ref Gudhi::multi_persistence::MultiFiltrationValue. + * + * @details Overloads `std::numeric_limits` such that: + * - `std::numeric_limits::has_infinity` returns `true`, + * - `std::numeric_limits::has_quiet_NaN` returns `std::numeric_limits::has_quiet_NaN`, + * - `std::numeric_limits::infinity(int)` returns + * @ref Multi_parameter_filtration::inf(int) "", + * - `std::numeric_limits::minus_infinity(int)` returns + * @ref Multi_parameter_filtration::minus_inf(int) "", + * - `std::numeric_limits::max(int num_param)` returns a @ref Multi_parameter_filtration + * with one generator of `num_param` parameters evaluated at value `std::numeric_limits::max()`, + * - `std::numeric_limits::quiet_NaN(int)` returns + * @ref Multi_parameter_filtration::nan(int) if `std::numeric_limits::has_quiet_NaN` + * and throws otherwise. + * + * Multi-critical filtrations are filtrations such that the lifetime of each object is union of positive cones in + * \f$\mathbb R^n\f$, e.g., + * - \f$ \{ x \in \mathbb R^2 : x \ge (1,2)\} \cap \{ x \in \mathbb R^2 : x \ge (2,1)\} \f$ is finitely critical, + * and more particularly 2-critical, while + * - \f$ \{ x \in \mathbb R^2 : x \ge \mathrm{epigraph}(y \mapsto e^{-y})\} \f$ is not. + * + * @tparam T Arithmetic type of an entry for one parameter of a filtration value. Has to be **signed** and + * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, + * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. + * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` + * can simply throw. Examples are the native types `double`, `float` and `int`. + * @tparam Co If `true`, reverses the poset order, i.e., the order \f$ \le \f$ in \f$ \mathbb R^n \f$ becomes + * \f$ \ge \f$. That is, the positive cones representing a lifetime become all negative instead. + * @tparam Ensure1Criticality If `true`, the methods ensure that the filtration value is always 1-critical by throwing + * or refusing to compile if a modification increases the number of generators. + */ +template +class Multi_parameter_filtration +{ + private: + using view_extents = extents; + using Viewer = Gudhi::Simple_mdspan; + + public: + using Underlying_container = std::vector; /**< Underlying container for values. */ + + // CONSTRUCTORS + + /** + * @brief Default constructor. Builds filtration value with one generator and given number of parameters. + * If Co is false, all values are at -inf, if Co is true, all values are at +inf. + * + * @param number_of_parameters If negative, takes the default value instead. Default value: 2. + */ + Multi_parameter_filtration(int number_of_parameters = 2) + : generators_(number_of_parameters < 0 ? 2 : number_of_parameters, _get_default_value()), + generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size()) + {} + + /** + * @brief Builds filtration value with one generator and given number of parameters. + * All values are initialized at the given value. + * + * @param number_of_parameters If negative, is set to 2 instead. + * @param value Initialization value for every value in the generator. + */ + Multi_parameter_filtration(int number_of_parameters, T value) + : generators_(number_of_parameters < 0 ? 2 : number_of_parameters, value), + generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size()) + {} + + /** + * @brief Builds filtration value with one generator that is initialized with the given range. The number of + * parameters are therefore deduced from the length of the range. + * + * @tparam ValueRange Range of types convertible to `T`. Should have a begin() and end() method. + * @param range Values of the generator. + */ + template , class = std::enable_if_t::has_begin> > + Multi_parameter_filtration(const ValueRange &range) + : generators_(range.begin(), range.end()), + generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size()) + {} + + /** + * @brief Builds filtration value with one generator that is initialized with the given range. The range is + * determined from the two given iterators. The number of parameters are therefore deduced from the distance + * between the two. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + */ + template + Multi_parameter_filtration(Iterator it_begin, Iterator it_end) + : generators_(it_begin, it_end), + generator_view_(generators_.data(), generators_.empty() ? 0 : 1, generators_.size()) + {} + + /** + * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$ + * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator, + * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple + * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by two iterators. + * + * @tparam Iterator Iterator type that has to satisfy the requirements of standard LegacyInputIterator and + * dereferenced elements have to be convertible to `T`. + * @param it_begin Iterator pointing to the start of the range. + * @param it_end Iterator pointing to the end of the range. + * @param number_of_parameters Negative values are associated to 0. + */ + template > > + Multi_parameter_filtration(Iterator it_begin, Iterator it_end, int number_of_parameters) + : generators_(it_begin, it_end), + generator_view_(generators_.data(), + number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters, + number_of_parameters) + { + if constexpr (Ensure1Criticality) { + if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$ + * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator, + * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple + * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by + * @ref Multi_parameter_filtration::Underlying_container "" and copied into the underlying container of the class. + * + * @param generators Values. + * @param number_of_parameters Negative values are associated to 0. + */ + Multi_parameter_filtration(const Underlying_container &generators, int number_of_parameters) + : generators_(generators), + generator_view_(generators_.data(), + number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters, + number_of_parameters) + { + GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(), + std::invalid_argument("Number of parameters cannot be 0 if the container is not empty.")); + + if constexpr (Ensure1Criticality) { + if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Builds filtration value with given number of parameters and values from the given range. Lets \f$ p \f$ + * be the number of parameters. The \f$ p \f$ first elements of the range have to correspond to the first generator, + * the \f$ p \f$ next elements to the second generator and so on... So the length of the range has to be a multiple + * of \f$ p \f$ and the number of generators will be \f$ length / p \f$. The range is represented by + * @ref Multi_parameter_filtration::Underlying_container "" and **moved** into the underlying container of the class. + * + * @param generators Values to move. + * @param number_of_parameters Negative values are associated to 0. + */ + Multi_parameter_filtration(Underlying_container &&generators, int number_of_parameters) + : generators_(std::move(generators)), + generator_view_(generators_.data(), + number_of_parameters <= 0 ? 0 : generators_.size() / number_of_parameters, + number_of_parameters) + { + GUDHI_CHECK(number_of_parameters > 0 || generators_.empty(), + std::invalid_argument("Number of parameters cannot be 0 if the container is not empty.")); + + if constexpr (Ensure1Criticality) { + if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Copy constructor. + */ + Multi_parameter_filtration(const Multi_parameter_filtration &other) + : generators_(other.generators_), + generator_view_(generators_.data(), other.num_generators(), other.num_parameters()) + {} + + /** + * @brief Copy constructor. + * + * @tparam U Type convertible into `T`. + */ + template + Multi_parameter_filtration(const Multi_parameter_filtration &other) + : generators_(other.begin(), other.end()), + generator_view_(generators_.data(), other.num_generators(), other.num_parameters()) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (generator_view_.extent(0) != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + } + + /** + * @brief Move constructor. + */ + Multi_parameter_filtration(Multi_parameter_filtration &&other) noexcept + : generators_(std::move(other.generators_)), + generator_view_(generators_.data(), other.num_generators(), other.num_parameters()) + {} + + ~Multi_parameter_filtration() = default; + + /** + * @brief Assign operator. + */ + Multi_parameter_filtration &operator=(const Multi_parameter_filtration &other) + { + generators_ = other.generators_; + generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters()); + return *this; + } + + /** + * @brief Assign operator. + * + * @tparam U Type convertible into `T`. + */ + template + Multi_parameter_filtration &operator=(const Multi_parameter_filtration &other) + { + if constexpr (Ensure1Criticality && !OtherEnsure1Criticality) { + if (other.num_generators() != 1) throw std::logic_error("Multiparameter filtration value is not 1-critical."); + } + generators_ = Underlying_container(other.begin(), other.end()); + generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters()); + return *this; + } + + /** + * @brief Move assign operator. + */ + Multi_parameter_filtration &operator=(Multi_parameter_filtration &&other) noexcept + { + generators_ = std::move(other.generators_); + generator_view_ = Viewer(generators_.data(), other.num_generators(), other.num_parameters()); + return *this; + } + + /** + * @brief Swap operator. + */ + friend void swap(Multi_parameter_filtration &f1, Multi_parameter_filtration &f2) noexcept + { + f1.generators_.swap(f2.generators_); + swap(f1.generator_view_, f2.generator_view_); + } + + // VECTOR-LIKE + + using value_type = T; /**< Value type. */ + using size_type = typename Underlying_container::size_type; /**< Size type. */ + using difference_type = typename Underlying_container::difference_type; /**< Difference type. */ + using reference = value_type &; /**< Reference type. */ + using const_reference = const value_type &; /**< Const reference type. */ + using pointer = typename Underlying_container::pointer; /**< Pointer type. */ + using const_pointer = typename Underlying_container::const_pointer; /**< Const pointer type. */ + using iterator = typename Underlying_container::iterator; /**< Iterator type. */ + using const_iterator = typename Underlying_container::const_iterator; /**< Const iterator type. */ + using reverse_iterator = typename Underlying_container::reverse_iterator; /**< Reverse iterator type. */ + using const_reverse_iterator = typename Underlying_container::const_reverse_iterator; /**< Const reverse iterator. */ + + /** + * @brief Returns reference to value of parameter `p` of generator `g`. + */ + reference operator()(size_type g, size_type p) { return generator_view_(g, p); } + + /** + * @brief Returns const reference to value of parameter `p` of generator `g`. + */ + const_reference operator()(size_type g, size_type p) const { return generator_view_(g, p); } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + reference operator[](const IndexRange &indices) + { + GUDHI_CHECK(indices.size() == 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return generator_view_(g, *(++it)); + } + + /** + * @brief Let \f$ g \f$ be the first value in `indices` and \f$ p \f$ the second value. + * Returns reference to value of parameter \f$ p \f$ of generator \f$ g \f$. + * + * @tparam IndexRange Range with a begin() and size() method. + * @param indices Range with at least two elements. The first element should correspond to the generator number and + * the second element to the parameter number. + */ + template , + class = std::enable_if_t::has_begin> > + const_reference operator[](const IndexRange &indices) const + { + GUDHI_CHECK(indices.size() == 2, + std::invalid_argument( + "Exactly 2 indices allowed only: first the generator number, second the parameter number.")); + auto it = indices.begin(); + size_type g = *it; + return generator_view_(g, *(++it)); + } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first + * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on. + * + * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour + * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to + * ensure this property. + */ + iterator begin() noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first + * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on. + */ + const_iterator begin() const noexcept { return generators_.begin(); } + + /** + * @brief Returns an iterator pointing the begining of the underlying container. The @ref num_parameters() first + * elements corresponds to the first generator, the @ref num_parameters() next to the second and so on. + */ + const_iterator cbegin() const noexcept { return generators_.cbegin(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + iterator end() noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator end() const noexcept { return generators_.end(); } + + /** + * @brief Returns an iterator pointing the end of the underlying container. + */ + const_iterator cend() const noexcept { return generators_.cend(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the + * @ref num_parameters() next to the second to last and so on. + * + * @warning If a generator is modified and the new set of generators is not minimal or not sorted, the behaviour + * of most methods is undefined. It is possible to call @ref simplify() after construction if there is a doubt to + * ensure this property. + */ + reverse_iterator rbegin() noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the + * @ref num_parameters() next to the second to last and so on. + */ + const_reverse_iterator rbegin() const noexcept { return generators_.rbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the first element from the back of the underlying container. + * The @ref num_parameters() first elements corresponds to the last generator (in parameter reverse order), the + * @ref num_parameters() next to the second to last and so on. + */ + const_reverse_iterator crbegin() const noexcept { return generators_.crbegin(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + reverse_iterator rend() noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator rend() const noexcept { return generators_.rend(); } + + /** + * @brief Returns a reverse iterator pointing to the end of the reversed underlying container. + */ + const_reverse_iterator crend() const noexcept { return generators_.crend(); } + + /** + * @brief Returns the size of the underlying container. Corresponds exactly to @ref num_entries(), but enables to use + * the class as a classic range with a `begin`, `end` and `size` method. + */ + size_type size() const noexcept { return generators_.size(); } + + /** + * @brief Reserves space for the given number of generators in the underlying container. Does nothing if + * `Ensure1Criticality` is true. + */ + void reserve([[maybe_unused]] size_type number_of_generators) + { + if constexpr (Ensure1Criticality) { + return; + } else { + generators_.reserve(num_parameters() * number_of_generators); + } + } + + // CONVERTERS + + // like numpy + /** + * @brief Returns a copy with entries casted into the type given as template parameter. + * + * @tparam U New type for the entries. + * @tparam OCo New value for `Co`. Default value: `Co`. + * @tparam OEns New value for `Ensure1Criticality`. Note that if `OEns` is set to true and the value is not + * 1-critical, the method will throw. Default value: `Ensure1Criticality`. + * @return Copy with new entry type. + */ + template + Multi_parameter_filtration as_type() const + { + std::vector out(generators_.begin(), generators_.end()); + return Multi_parameter_filtration(std::move(out), num_parameters()); + } + + // ACCESS + + /** + * @brief Returns the number of parameters in the filtration value. + */ + size_type num_parameters() const { return generator_view_.extent(1); } + + /** + * @brief Returns the number of generators in the filtration value, i.e. the criticality of the element. + */ + size_type num_generators() const + { + if constexpr (Ensure1Criticality) { + return 1; // for possible optimizations? If there is none, we can just keep the other version + } else { + return generator_view_.extent(0); + } + } + + /** + * @brief Returns the total number of values in the filtration value, that is, + * @ref num_parameters() * @ref num_generators(). + */ + size_type num_entries() const { return generators_.size(); } + + /** + * @brief Returns a filtration value with given number of parameters for which @ref is_plus_inf() returns `true`. + */ + static Multi_parameter_filtration inf(int number_of_parameters) + { + return Multi_parameter_filtration(number_of_parameters, T_inf); + } + + /** + * @brief Returns a filtration value with given number of parameters for which @ref is_minus_inf() returns `true`. + */ + static Multi_parameter_filtration minus_inf(int number_of_parameters) + { + return Multi_parameter_filtration(number_of_parameters, T_m_inf); + } + + /** + * @brief If `std::numeric_limits::has_quiet_NaN` is true, returns a filtration value with given number of + * parameters for which @ref is_nan() returns `true`. Otherwise, throws. + */ + static Multi_parameter_filtration nan(int number_of_parameters) + { + if constexpr (std::numeric_limits::has_quiet_NaN) { + return Multi_parameter_filtration(number_of_parameters, std::numeric_limits::quiet_NaN()); + } else { + throw std::logic_error("No NaN value exists."); + } + } + + // DESCRIPTORS + + /** + * @brief Returns value of `Ensure1Criticality`. + */ + static constexpr bool ensures_1_criticality() { return Ensure1Criticality; } + + /** + * @brief Returns value of `Co`. + */ + static constexpr bool has_negative_cones() { return Co; } + + /** + * @brief Returns `true` if and only if the filtration value is considered as plus infinity. + */ + [[nodiscard]] bool is_plus_inf() const + { + for (const T &v : generators_) { + if (v != T_inf) return false; + } + return true; + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as minus infinity. + */ + [[nodiscard]] bool is_minus_inf() const + { + for (const T &v : generators_) { + if (v != T_m_inf) return false; + } + return true; + } + + /** + * @brief Returns `true` if and only if the filtration value is considered as NaN. + */ + [[nodiscard]] bool is_nan() const + { + if constexpr (std::numeric_limits::has_quiet_NaN) { + for (const auto &v : generators_) { + if (!std::isnan(v)) return false; + } + return true; + } else { + return false; + } + } + + /** + * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as plus infinity, + * minus infinity or NaN. + */ + [[nodiscard]] bool is_finite() const + { + bool isInf = true, isMinusInf = true, isNan = true; + for (const auto &v : generators_) { + if (v != T_inf) isInf = false; + if (v != T_m_inf) isMinusInf = false; + if (!_is_nan(v)) isNan = false; + if (!isInf && !isMinusInf && !isNan) return true; + } + return false; + } + + // COMPARAISON OPERATORS + + /** + * @brief Returns `true` if and only if the first argument is lexicographically strictly less than the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator< "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_strict_less_than_lexicographically(const Multi_parameter_filtration &a, + const Multi_parameter_filtration &b) + { + if (&a == &b) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + for (std::size_t i = 0U; i < a.num_parameters() * std::min(a.num_generators(), b.num_generators()); ++i) { + std::size_t iA = i; + std::size_t iB = i; + if constexpr (inverse) { + iA = a.generators_.size() - 1 - i; + iB = b.generators_.size() - 1 - i; + } + if (_is_nan(a.generators_[iA]) && !_is_nan(b.generators_[iB])) return false; + if (_is_nan(b.generators_[iB])) return true; + if (a.generators_[iA] < b.generators_[iB]) return true; + if (b.generators_[iB] < a.generators_[iA]) return false; + } + return a.num_generators() < b.num_generators(); + } + + /** + * @brief Returns `true` if and only if the first argument is lexicographically less than or equal to the second + * argument. The "words" considered for the lexicographical order are all the generators concatenated together + * in order of generator index and then in order of parameter index. Different from @ref operator<= "", this order + * is total. + * + * @tparam inverse If true, the parameter index and generator index order is inverted. + */ + template + friend bool is_less_or_equal_than_lexicographically(const Multi_parameter_filtration &a, + const Multi_parameter_filtration &b) + { + if (&a == &b) return true; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + for (std::size_t i = 0U; i < a.num_parameters() * std::min(a.num_generators(), b.num_generators()); ++i) { + std::size_t iA = i; + std::size_t iB = i; + if constexpr (inverse) { + iA = a.generators_.size() - 1 - i; + iB = b.generators_.size() - 1 - i; + } + if (_is_nan(a.generators_[iA]) && !_is_nan(b.generators_[iB])) return false; + if (_is_nan(b.generators_[iB])) return true; + if (a.generators_[iA] < b.generators_[iB]) return true; + if (b.generators_[iB] < a.generators_[iA]) return false; + } + return a.num_generators() <= b.num_generators(); + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are strictly contained in the + * cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator<(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) + { + if (&a == &b) return false; + + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + if (a.num_generators() == 0 || b.num_generators() == 0) return false; + + const auto &view_a = a.generator_view_; + const auto &view_b = b.generator_view_; + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + if (_first_dominates(view_a, 0, view_b, 0)) return false; + return _strictly_contains(view_a, 0, view_b, 0); + } else { + for (std::size_t i = 0U; i < b.num_generators(); ++i) { + // for each generator in b, verify if it is strictly in the cone of at least one generator of a + bool isContained = false; + for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) { + // lexicographical order, so if a[j][0] dom b[j][0], than a[j'] can never strictly contain b[i] for all + // j' > j. + if (_first_dominates(view_a, j, view_b, i)) return false; + isContained = _strictly_contains(view_a, j, view_b, i); + } + if (!isContained) return false; + } + return true; + } + } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are strictly contained in the + * cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator<=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) + { + GUDHI_CHECK(a.num_parameters() == b.num_parameters(), + std::invalid_argument("Only filtration values with same number of parameters can be compared.")); + + if (a.num_generators() == 0 || b.num_generators() == 0) return false; + if (a.is_nan() || b.is_nan()) return false; + if (&a == &b) return true; + + const auto &view_a = a.generator_view_; + const auto &view_b = b.generator_view_; + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + if (_first_strictly_dominates(view_a, 0, view_b, 0)) return false; + return _contains(view_a, 0, view_b, 0); + } else { + // check if this curves is below other's curve + // ie for each guy in this, check if there is a guy in other that dominates him + for (std::size_t i = 0U; i < b.num_generators(); ++i) { + // for each generator in b, verify if it is in the cone of at least one generator of a + bool isContained = false; + for (std::size_t j = 0U; j < a.num_generators() && !isContained; ++j) { + // lexicographical order, so if a[j][0] strictly dom b[j][0], than a[j'] can never contain b[i] for all + // j' > j. + if (_first_strictly_dominates(view_a, j, view_b, i)) return false; + isContained = _contains(view_a, j, view_b, i); + } + if (!isContained) return false; + } + return true; + } + } + + /** + * @brief Returns `true` if and only if the cones generated by @p b are contained in or are (partially) + * equal to the cones generated by @p a (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false + * does **not** imply \f$ a == b \f$. If a total order is needed, use @ref is_strict_less_than_lexicographically + * instead. + */ + friend bool operator>(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return b < a; } + + /** + * @brief Returns `true` if and only if the cones generated by @p a are contained in or are (partially) + * equal to the cones generated by @p b (recall that the cones are positive if `Co` is false and negative if `Co` is + * true). + * Both @p a and @p b have to have the same number of parameters. + * + * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return + * `false`. If a total order is needed, use @ref is_less_or_equal_than_lexicographically instead. + */ + friend bool operator>=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return b <= a; } + + /** + * @brief Returns `true` if and only if for each \f$ i,j \f$, \f$ a(i,j) \f$ is equal to \f$ b(i,j) \f$. + */ + friend bool operator==(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) + { + if (&a == &b) return true; + // assumes lexicographical order for both + return a.generators_ == b.generators_; + } + + /** + * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. + */ + friend bool operator!=(const Multi_parameter_filtration &a, const Multi_parameter_filtration &b) { return !(a == b); } + + // ARITHMETIC OPERATORS + + // opposite + /** + * @brief Returns a filtration value such that an entry at index \f$ i,j \f$ is equal to \f$ -f(i,j) \f$. + * + * Used conventions: + * - \f$ -NaN = NaN \f$. + * + * @param f Value to opposite. + * @return The opposite of @p f. + */ + friend Multi_parameter_filtration operator-(const Multi_parameter_filtration &f) + { + using F = Multi_parameter_filtration; + + Underlying_container result(f.generators_); + std::for_each(result.begin(), result.end(), [](T &v) { + if (v == F::T_inf) + v = F::T_m_inf; + else if (v == F::T_m_inf) + v = F::T_inf; + else + v = -v; + }); + return Multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + // subtraction + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Multi_parameter_filtration operator-(Multi_parameter_filtration f, const ValueRange &r) + { + f -= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) - f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ -f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the subtraction. + * @param f Second element of the subtraction. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_filtration operator-(const ValueRange &r, Multi_parameter_filtration f) + { + f._apply_operation(r, [](T &valF, const T &valR) { + valF = -valF; + _add(valF, valR); + }); + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Multi_parameter_filtration operator-(Multi_parameter_filtration f, const T &val) + { + f -= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val - f(g,p) \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the subtraction. + * @param f Second element of the subtraction. + */ + friend Multi_parameter_filtration operator-(const T &val, Multi_parameter_filtration f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + valF = -valF; + _add(valF, valR); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) - r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the subtraction. + * @param r Second element of the subtraction. + */ + template ::has_begin> > + friend Multi_parameter_filtration &operator-=(Multi_parameter_filtration &f, const ValueRange &r) + { + f._apply_operation(r, [](T &valF, const T &valR) { _subtract(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Multi_parameter_filtration &operator-=(Multi_parameter_filtration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _subtract(valF, valR); }); + return f; + } + + // addition + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Multi_parameter_filtration operator+(Multi_parameter_filtration f, const ValueRange &r) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) + f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the addition. + * @param f Second element of the addition. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_filtration operator+(const ValueRange &r, Multi_parameter_filtration f) + { + f += r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Multi_parameter_filtration operator+(Multi_parameter_filtration f, const T &val) + { + f += val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val + f(g,p) \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the addition. + * @param f Second element of the addition. + */ + friend Multi_parameter_filtration operator+(const T &val, Multi_parameter_filtration f) + { + f += val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) + r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the addition. + * @param r Second element of the addition. + */ + template ::has_begin> > + friend Multi_parameter_filtration &operator+=(Multi_parameter_filtration &f, const ValueRange &r) + { + f._apply_operation(r, [](T &valF, const T &valR) { _add(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Multi_parameter_filtration &operator+=(Multi_parameter_filtration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _add(valF, valR); }); + return f; + } + + // multiplication + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Multi_parameter_filtration operator*(Multi_parameter_filtration f, const ValueRange &r) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) * f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the multiplication. + * @param f Second element of the multiplication. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_filtration operator*(const ValueRange &r, Multi_parameter_filtration f) + { + f *= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Multi_parameter_filtration operator*(Multi_parameter_filtration f, const T &val) + { + f *= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val * f(g,p) \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the multiplication. + * @param f Second element of the multiplication. + */ + friend Multi_parameter_filtration operator*(const T &val, Multi_parameter_filtration f) + { + f *= val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) * r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the multiplication. + * @param r Second element of the multiplication. + */ + template ::has_begin> > + friend Multi_parameter_filtration &operator*=(Multi_parameter_filtration &f, const ValueRange &r) + { + f._apply_operation(r, [](T &valF, const T &valR) { _multiply(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Multi_parameter_filtration &operator*=(Multi_parameter_filtration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _multiply(valF, valR); }); + return f; + } + + // division + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Multi_parameter_filtration operator/(Multi_parameter_filtration f, const ValueRange &r) + { + f /= r; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ r(p) / f(g,p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param r First element of the division. + * @param f Second element of the division. + */ + template ::has_begin && + !std::is_same_v > > + friend Multi_parameter_filtration operator/(const ValueRange &r, Multi_parameter_filtration f) + { + f._apply_operation(r, [](T &valF, const T &valR) { + T tmp = valF; + valF = valR; + _divide(valF, tmp); + }); + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Multi_parameter_filtration operator/(Multi_parameter_filtration f, const T &val) + { + f /= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (g,p) \f$ is equal to \f$ val / f(g,p) \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the division. + * @param f Second element of the division. + */ + friend Multi_parameter_filtration operator/(const T &val, Multi_parameter_filtration f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + T tmp = valF; + valF = valR; + _divide(valF, tmp); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$, with + * \f$ 0 \leq g \leq num_generators \f$ and \f$ 0 \leq p \leq num_parameters \f$ is equal to \f$ f(g,p) / r(p) \f$ + * if \f$ p < length_r \f$ and to \f$ f(g,p) \f$ otherwise. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @tparam ValueRange Range with a begin() and end() method. + * @param f First element of the division. + * @param r Second element of the division. + */ + template ::has_begin> > + friend Multi_parameter_filtration &operator/=(Multi_parameter_filtration &f, const ValueRange &r) + { + f._apply_operation(r, [](T &valF, const T &valR) { _divide(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (g,p) \f$ is equal to \f$ f(g,p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Multi_parameter_filtration &operator/=(Multi_parameter_filtration &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { _divide(valF, valR); }); + return f; + } + + // MODIFIERS + + /** + * @brief Sets the number of generators. If there were less generators before, new generators with default values are + * constructed. If there were more generators before, the exceed of generators is destroyed (any generator with index + * higher or equal to @p g to be more precise). If @p g is zero, the methods does nothing. + * + * Fails to compile if `Ensure1Criticality` is true. + * + * @warning All new generators will be set to infinity (`Co` is true) or -infinity (`Co` is false). That is, the new + * filtration value is not minimal anymore. Make sure to fill them with real generators or to remove them before + * using other methods. + * + * @warning Be sure to call @ref simplify if necessary after initializing all the generators. Most methods will have + * an undefined behaviour if the set of generators is not minimal or sorted. + * + * @param g New number of generators. + */ + void set_num_generators(size_type g) + { + static_assert(!Ensure1Criticality, "Number of generators cannot be set for a 1-critical only filtration value."); + + if (g == 0) return; + generators_.resize(g * num_parameters(), _get_default_value()); + generator_view_.update_data(generators_.data()); // in case it was relocated + generator_view_.update_extent(0, g); + } + + /** + * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted. + * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that + * old generators disappear if they are overshadowed by the new one. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() method and the iterator + * type should satisfy the requirements of the standard `LegacyForwardIterator`. + * @param x New generator to add. Has to have the same number of parameters than @ref num_parameters(). + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool add_generator(const GeneratorRange &x) + { + return add_generator(x.begin(), x.end()); + } + + /** + * @brief Adds the given generator to the filtration value such that the set remains minimal and sorted. + * It is therefore possible that the generator is ignored if it does not generated any new lifetime or that + * old generators disappear if they are overshadowed by the new one. + * + * @tparam Iterator Iterator class satisfying the requirements of the standard `LegacyForwardIterator`. + * The dereferenced type has to be convertible to `T`. + * @param genStart Iterator pointing to the begining of the range. + * @param genEnd Iterator pointing to the end of the range. + * @return true If and only if the generator is actually added to the set of generators. + * @return false Otherwise. + */ + template + bool add_generator(Iterator genStart, Iterator genEnd) + { + GUDHI_CHECK(std::distance(genStart, genEnd) == static_cast(num_parameters()), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + const int newIndex = -1; + + std::size_t end = num_generators(); + std::vector indices(end); + std::iota(indices.begin(), indices.end(), 0); + + if (_generator_can_be_added(genStart, 0, end, indices)) { + indices.resize(end); + indices.push_back(newIndex); + _build_from(indices, newIndex, genStart, genEnd); + if constexpr (Ensure1Criticality) { + if (generator_view_.extent(0) != 1) + throw std::logic_error("Multiparameter filtration value is not 1-critical anymore."); + } + return true; + } + + return false; + } + + /** + * @brief Adds the given generator to the filtration value without any verifications or simplifications at the end + * of the set. + * + * Fails to compile if `Ensure1Criticality` is true. + * + * @warning If the resulting set of generators is not minimal or sorted after modification, some methods will have an + * undefined behaviour. Be sure to call @ref simplify() before using them. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x New generator to add. Must have the same number of parameters than @ref num_parameters(). + */ + template , + class = std::enable_if_t::has_begin> > + void add_guaranteed_generator(const GeneratorRange &x) + { + static_assert(!Ensure1Criticality, "Cannot add additional generator to a 1-critical only filtration value."); + + GUDHI_CHECK(x.size() == num_parameters(), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + generators_.insert(generators_.end(), x.begin(), x.end()); + generator_view_.update_data(generators_.data()); // in case it was relocated + generator_view_.update_extent(0, num_generators() + 1); + } + + /** + * @brief Simplifies the current set of generators such that it becomes minimal. Also orders it in increasing + * lexicographical order. Only necessary if generators were added "by hand" without verification either trough the + * constructor or with @ref add_guaranteed_generator "", etc. + */ + void simplify() + { + if constexpr (Ensure1Criticality) { + return; + } else { + std::size_t end = 0; + std::vector indices(num_generators()); + std::iota(indices.begin(), indices.end(), 0); + + for (std::size_t curr = 0; curr < num_generators(); ++curr) { + if (_generator_can_be_added( + generators_.begin() + generator_view_.mapping()(indices[curr], 0), 0, end, indices)) { + std::swap(indices[end], indices[curr]); + ++end; + } + } + + indices.resize(end); + _build_from(indices); + } + } + + /** + * @brief Removes all empty generators from the filtration value. If @p include_infinities is true, it also + * removes the generators at infinity or minus infinity. As empty generators are not possible (except if number of + * parameters is 0), the method does nothing except sorting the set of generators if @p include_infinities is false. + * Exists mostly for interface purposes. + * If the set of generators is empty after removals, it is set to minus infinity if `Co` is false or to infinity + * if `Co` is true. + * + * @warning If the resulting set of generators is not minimal after the removals/sorting, some methods will have an + * undefined behaviour. Be sure to call @ref simplify() before using them. + * + * @param include_infinities If true, removes also infinity values. + */ + void remove_empty_generators(bool include_infinities = false) + { + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + if (!include_infinities) return; + bool allNaN = true, allInf = true; + bool toEmpty = true; + for (size_type p = 0; p < num_parameters() && toEmpty; ++p) { + if (!_is_nan(generators_[p])) allNaN = false; + if constexpr (Co) { + if (generators_[p] != T_m_inf) allInf = false; + } else { + if (generators_[p] != T_inf) allInf = false; + } + if (!allNaN && !allInf) toEmpty = false; + } + if (toEmpty) generators_.clear(); + } else { + std::vector indices; + indices.reserve(num_generators()); + for (int i = 0; i < static_cast(num_generators()); ++i) { + if (!include_infinities || _is_finite(i)) indices.push_back(i); + } + _build_from(indices); // sorts + } + + if (generators_.empty()) { + generators_.resize(num_parameters(), _get_default_value()); + generator_view_.update_data(generators_.data()); // in case it was relocated + generator_view_.update_extent(0, 1); + } + } + + /** + * @brief Sets each generator of the filtration value to the least common upper bound between it and the given value. + * + * More formally, it pushes the current generator to the cone \f$ \{ y \in \mathbb R^n : y \ge x \} \f$ + * originating in \f$ x \f$. The resulting value corresponds to the intersection of both + * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \ge this \} \cap \{ y \in \mathbb R^n : y \ge x \} \f$. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x Range towards to push. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool push_to_least_common_upper_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + GUDHI_CHECK(x.size() == num_parameters(), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + bool xIsInf = true, xIsMinusInf = true, xIsNaN = true; + bool thisIsInf = true, thisIsMinusInf = true, thisIsNaN = true; + + // if one is not finite, we can avoid the heavy simplification process + _get_infinity_statuses(generator_view_, x, thisIsInf, thisIsMinusInf, thisIsNaN, xIsInf, xIsMinusInf, xIsNaN); + + if (thisIsInf || thisIsNaN || xIsNaN || xIsMinusInf || (xIsInf && exclude_infinite_values)) return false; + + if (xIsInf || thisIsMinusInf) { + generators_ = Underlying_container(x.begin(), x.end()); + generator_view_.update_data(generators_.data()); // in case it was relocated + generator_view_.update_extent(0, 1); + return true; + } + + bool modified = false; + + auto push_generator_value = [&](T &val, T valX) { + if (exclude_infinite_values && (valX == T_inf || valX == T_m_inf)) return; + modified |= val < valX; + val = valX > val ? valX : val; + }; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + auto it = x.begin(); + for (size_type p = 0; p < num_parameters(); ++p) { + push_generator_value(generators_[p], *it); + ++it; + } + } else { + for (size_type g = 0; g < num_generators(); ++g) { + auto it = x.begin(); + for (size_type p = 0; p < num_parameters(); ++p) { + push_generator_value(generator_view_(g, p), *it); + ++it; + } + } + + if (modified && num_generators() > 1) simplify(); + } + + return modified; + } + + /** + * @brief Sets each generator of the filtration value to the greatest common lower bound between it and the given + * value. + * + * More formally, it pulls the current generator to the cone \f$ \{ y \in \mathbb R^n : y \le x \} \f$ + * originating in \f$ x \f$. The resulting value corresponds to the intersection of both + * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \le this \} \cap \{ y \in \mathbb R^n : y \le x \} \f$. + * + * @tparam GeneratorRange Range of elements convertible to `T`. Must have a begin(), end() and size() method. + * @param x Range towards to pull. Has to have as many elements than @ref num_parameters(). + * @param exclude_infinite_values If true, values at infinity or minus infinity are not affected. + * @return true If the filtration value was actually modified. + * @return false Otherwise. + */ + template , + class = std::enable_if_t::has_begin> > + bool pull_to_greatest_common_lower_bound(const GeneratorRange &x, bool exclude_infinite_values = false) + { + GUDHI_CHECK(x.size() == num_parameters(), + std::invalid_argument("Wrong range size. Should correspond to the number of parameters.")); + + bool xIsInf = true, xIsMinusInf = true, xIsNaN = true; + bool thisIsInf = true, thisIsMinusInf = true, thisIsNaN = true; + + // if one is not finite, we can avoid the heavy simplification process + _get_infinity_statuses(generator_view_, x, thisIsInf, thisIsMinusInf, thisIsNaN, xIsInf, xIsMinusInf, xIsNaN); + + if (xIsInf || thisIsNaN || xIsNaN || thisIsMinusInf || (xIsMinusInf && exclude_infinite_values)) return false; + + if (thisIsInf || xIsMinusInf) { + generators_ = Underlying_container(x.begin(), x.end()); + generator_view_.update_data(generators_.data()); // in case it was relocated + generator_view_.update_extent(0, 1); + return true; + } + + bool modified = false; + + auto pull_generator_value = [&](T &val, T valX) { + if (exclude_infinite_values && (valX == T_inf || valX == T_m_inf)) return; + modified |= val > valX; + val = valX < val ? valX : val; + }; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + auto it = x.begin(); + for (size_type p = 0; p < num_parameters(); ++p) { + pull_generator_value(generators_[p], *it); + ++it; + } + } else { + for (size_type g = 0; g < num_generators(); ++g) { + auto it = x.begin(); + for (size_type p = 0; p < num_parameters(); ++p) { + pull_generator_value(generator_view_(g, p), *it); + ++it; + } + } + + if (modified && num_generators() > 1) simplify(); + } + + return modified; + } + + /** + * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. + * + * @tparam OneDimArray A range of values convertible into `T` ordered by increasing value. Has to implement + * a begin, end and operator[] method. + * @param grid Vector of @p OneDimArray with size at least number of filtration parameters. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. + */ + template + void project_onto_grid(const std::vector &grid, bool coordinate = true) + { + GUDHI_CHECK( + grid.size() >= num_parameters(), + std::invalid_argument("The grid should not be smaller than the number of parameters in the filtration value.")); + + auto project_generator_value = [&](T &val, const OneDimArray &filtration) { + typename OneDimArray::value_type _val = static_cast(val); + auto d = std::distance( + filtration.begin(), + std::lower_bound(filtration.begin(), filtration.end(), _val)); + if (std::abs(_val - filtration[d]) > + std::abs(_val - filtration[d == 0 ? 0 : d - 1])) + --d; + val = coordinate ? static_cast(d) : static_cast(filtration[d]); + }; + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + for (size_type p = 0; p < num_parameters(); ++p) { + project_generator_value(generators_[p], grid[p]); + } + } else { + for (size_type g = 0; g < num_generators(); ++g) { + for (size_type p = 0; p < num_parameters(); ++p) { + project_generator_value(generator_view_(g, p), grid[p]); + } + } + + if (!coordinate && num_generators() > 1) simplify(); + } + } + + // FONCTIONNALITIES + + /** + * @brief Returns a generator with the minimal values of all parameters in any generator of the given filtration + * value. That is, the greatest lower bound of all generators. + */ + friend Multi_parameter_filtration factorize_below(const Multi_parameter_filtration &f) + { + if (f.num_generators() <= 1) return f; + + bool nan = true; + Underlying_container result(f.num_parameters(), T_inf); + for (size_type p = 0; p < f.num_parameters(); ++p) { + for (size_type g = 0; g < f.num_generators(); ++g) { + T val = f(g, p); + if (!_is_nan(val)) { + nan = false; + result[p] = val < result[p] ? val : result[p]; + } + } + if (nan) + result[p] = std::numeric_limits::quiet_NaN(); + else + nan = true; + } + return Multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + /** + * @brief Returns a generator with the maximal values of all parameters in any generator of the given filtration + * value. That is, the least upper bound of all generators. + */ + friend Multi_parameter_filtration factorize_above(const Multi_parameter_filtration &f) + { + if (f.num_generators() <= 1) return f; + + bool nan = true; + Underlying_container result(f.num_parameters(), T_m_inf); + for (size_type p = 0; p < f.num_parameters(); ++p) { + for (size_type g = 0; g < f.num_generators(); ++g) { + T val = f(g, p); + if (!_is_nan(val)) { + nan = false; + result[p] = val > result[p] ? val : result[p]; + } + } + if (nan) + result[p] = std::numeric_limits::quiet_NaN(); + else + nan = true; + } + return Multi_parameter_filtration(std::move(result), f.num_parameters()); + } + + /** + * @brief Computes the smallest (resp. the greatest if `Co` is true) scalar product of the all generators with the + * given vector. + * + * @tparam U Arithmetic type of the result. Default value: `T`. + * @param f Filtration value. + * @param x Vector of coefficients. + * @return Scalar product of @p f with @p x. + */ + template + friend U compute_linear_projection(const Multi_parameter_filtration &f, const std::vector &x) + { + auto project_generator = [&](size_type g) -> U { + U projection = 0; + std::size_t size = std::min(x.size(), f.num_parameters()); + for (std::size_t i = 0; i < size; i++) projection += x[i] * static_cast(f(g, i)); + return projection; + }; + + if (f.num_generators() == 1) return project_generator(0); + + if constexpr (Co) { + U projection = std::numeric_limits::lowest(); + for (size_type g = 0; g < f.num_generators(); ++g) { + // Order in the max important to spread possible NaNs + projection = std::max(project_generator(g), projection); + } + return projection; + } else { + U projection = std::numeric_limits::max(); + for (size_type g = 0; g < f.num_generators(); ++g) { + // Order in the min important to spread possible NaNs + projection = std::min(project_generator(g), projection); + } + return projection; + } + } + + /** + * @brief Computes the euclidean distance from the first parameter to the second parameter as the minimum of + * all Euclidean distances between a generator of @p f and a generator of @p other. + * + * @param f Source filtration value. + * @param other Target filtration value. + * @return Euclidean distance between @p f and @p other. + */ + template + friend U compute_euclidean_distance_to(const Multi_parameter_filtration &f, const Multi_parameter_filtration &other) + { + GUDHI_CHECK(f.num_parameters() == other.num_parameters(), + std::invalid_argument("We cannot compute the distance between two points of different dimensions.")); + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + return _compute_frobenius_norm(f.num_parameters(), + [&](size_type p) -> T { return f.generators_[p] - other.generators_[p]; }); + } else { + U res = std::numeric_limits::max(); + for (size_type g1 = 0; g1 < f.num_generators(); ++g1) { + for (size_type g2 = 0; g2 < other.num_generators(); ++g2) { + // Euclidean distance as a Frobenius norm with matrix 1 x p and values 'f(g1, p) - other(g2, p)' + // Order in the min important to spread possible NaNs + res = std::min( + _compute_frobenius_norm(f.num_parameters(), [&](size_type p) -> T { return f(g1, p) - other(g2, p); }), + res); + } + } + return res; + } + } + + /** + * @brief Computes the norm of the given filtration value. + * + * The filtration value is seen as a \f$ num_generators x num_parameters \f$ matrix and a standard Frobenius norm + * is computed from it: the square root of the sum of the squares of all elements in the matrix. + * + * @param f Filtration value. + * @return The norm of @p f. + */ + template + friend U compute_norm(const Multi_parameter_filtration &f) + { + // Frobenius norm with matrix g x p based on Euclidean norm + return _compute_frobenius_norm(f.num_entries(), [&](size_type i) -> T { return f.generators_[i]; }); + } + + /** + * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries + * in the given filtration value. + * The grid has to be represented as a vector of vectors of ordered values convertible into `OutValue`. An index + * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in a generator of the filtration + * value. The ranges correspond to the possible values of the parameters, ordered by increasing value, forming + * therefore all together a 2D grid. + * + * @tparam OutValue Signed arithmetic type. Default value: std::int32_t. + * @tparam U Type which is convertible into `OutValue`. + * @param f Filtration value to project. + * @param grid Vector of vectors to project into. + * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is, + * the projection of \f$ f(g,p) \f$ is \f$ grid[p][out(g,p)] \f$. + */ + template + friend Multi_parameter_filtration compute_coordinates_in_grid( + Multi_parameter_filtration f, + const std::vector > &grid) + { + // TODO: by replicating the code of "project_onto_grid", this could be done with just one copy + // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really + // necessary in the use cases. To see later. + f.project_onto_grid(grid); + if constexpr (std::is_same_v) { + return f; + } else { + return f.as_type(); + } + } + + /** + * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration + * value. That is, if \f$ out \f$ is the result, \f$ out(g,p) = grid[p][f(g,p)] \f$. Assumes therefore, that the + * values stored in the filtration value corresponds to indices existing in the given grid. + * + * @tparam U Signed arithmetic type. + * @param f Filtration value storing coordinates compatible with `grid`. + * @param grid Vector of vector. + * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out(g,p) = grid[p][f(g,p)] \f$. + */ + template + friend Multi_parameter_filtration evaluate_coordinates_in_grid( + const Multi_parameter_filtration &f, + const std::vector > &grid) + { + GUDHI_CHECK(grid.size() >= f.num_parameters(), + std::invalid_argument( + "The size of the grid should correspond to the number of parameters in the filtration value.")); + + U grid_inf = Multi_parameter_filtration::T_inf; + std::vector outVec(f.num_entries()); + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + if constexpr (Ensure1Criticality) { + for (size_type p = 0; p < f.num_parameters(); ++p) { + const std::vector &filtration = grid[p]; + const T &c = f.generators_[p]; + outVec[p] = (c == T_inf ? grid_inf : filtration[c]); + } + } else { + for (size_type g = 0; g < f.num_generators(); ++g) { + for (size_type p = 0; p < f.num_parameters(); ++p) { + const std::vector &filtration = grid[p]; + const T &c = f(g, p); + outVec[f.generator_view_.mapping()(g, p)] = (c == T_inf ? grid_inf : filtration[c]); + } + } + } + + Multi_parameter_filtration out(std::move(outVec), f.num_parameters()); + if constexpr (!Ensure1Criticality) + if (out.num_generators() > 1) out.simplify(); + return out; + } + + // UTILITIES + + /** + * @brief Outstream operator. + */ + friend std::ostream &operator<<(std::ostream &stream, const Multi_parameter_filtration &f) + { + const size_type num_gen = f.num_generators(); + const size_type num_param = f.num_parameters(); + + stream << "( k = " << num_gen << " ) ( p = " << num_param << " ) [ "; + for (size_type g = 0; g < num_gen; ++g) { + stream << "["; + for (size_type p = 0; p < num_param; ++p) { + stream << f(g, p); + if (p < num_param - 1) stream << ", "; + } + stream << "]"; + if (g < num_gen - 1) stream << "; "; + } + stream << " ]"; + + return stream; + } + + /** + * @brief Instream operator. + */ + friend std::istream &operator>>(std::istream &stream, Multi_parameter_filtration &f) + { + size_type num_gen; + size_type num_param; + char delimiter; + stream >> delimiter; // ( + stream >> delimiter; // k + stream >> delimiter; // = + stream >> num_gen; + if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration."); + stream >> delimiter; // ) + stream >> delimiter; // ( + stream >> delimiter; // p + stream >> delimiter; // = + stream >> num_param; + if (!stream.good()) throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration."); + f.generators_.resize(num_gen * num_param); + f.generator_view_ = Viewer(f.generators_.data(), num_gen, num_param); + stream >> delimiter; // ) + stream >> delimiter; // [ + if (delimiter != '[') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration."); + if (num_gen == 0) return stream; + for (size_type i = 0; i < num_gen; ++i) { + stream >> delimiter; // [ + for (size_type j = 0; j < num_param; ++j) { + f(i, j) = _get_value(stream); + if (!stream.good()) + throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration."); + stream >> delimiter; // , or last ] + } + stream >> delimiter; // ; or last ] + } + if (delimiter != ']') throw std::invalid_argument("Invalid incoming stream format for Multi_parameter_filtration."); + + return stream; + } + + /** + * @brief Returns true if and only if the given filtration value is at plus infinity. + */ + friend bool is_positive_infinity(const Multi_parameter_filtration &f) + { + return f.is_plus_inf(); + } + + /** + * @brief Adds the generators of the second argument to the first argument. If `Ensure1Criticality` is true, + * the method assumes that the two filtration values are comparable, that is, that the result of the union is also + * 1-critical. A check for this is only done in Debug Mode, as it is costly. + * + * @param f1 Filtration value to modify. + * @param f2 Filtration value to merge with the first one. Should have the same number of parameters than the other. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool unify_lifetimes(Multi_parameter_filtration &f1, const Multi_parameter_filtration &f2) + { + GUDHI_CHECK(f1.num_parameters() == f2.num_parameters(), + std::invalid_argument("Cannot unify two filtration values with different number of parameters.")); + + // TODO: verify if this really makes a differences in the 1-critical case, otherwise just keep the general case + // if general case is kept: add (num_gen == 1) test to throw if unification is not 1-critical anymore. + if constexpr (Ensure1Criticality) { + // WARNING: costly check + GUDHI_CHECK( + f1 <= f2 || f2 <= f1, + std::invalid_argument("When 1-critical only, two non-comparable filtration values cannot be unified.")); + + if constexpr (Co) { + return f1.push_to_least_common_upper_bound(f2); + } else { + return f1.pull_to_greatest_common_lower_bound(f2); + } + } else { + bool modified = false; + for (size_type g = 0; g < f2.num_generators(); ++g) { + auto start = f2.begin(); + start += g * f2.num_parameters(); + auto end = start + f2.num_parameters(); + modified |= f1.add_generator(start, end); + } + return modified; + } + } + + /** + * @brief Stores in the first argument the origins of the cones in the intersection of the positive + * (negative if `Co` is true) cones generated by the two arguments. + * + * @param f1 First set of cones which will be modified. + * @param f2 Second set of cones. Should have the same number of parameters than the first one. + * @return true If the first argument was actually modified. + * @return false Otherwise. + */ + friend bool intersect_lifetimes(Multi_parameter_filtration &f1, const Multi_parameter_filtration &f2) + { + GUDHI_CHECK(f1.num_parameters() == f2.num_parameters(), + std::invalid_argument("Cannot intersect two filtration values with different number of parameters.")); + + if constexpr (Ensure1Criticality) { + if constexpr (Co) { + return f1.pull_to_greatest_common_lower_bound(f2); + } else { + return f1.push_to_least_common_upper_bound(f2); + } + } else { + bool f2IsInf = true, f2IsMinusInf = true, f2IsNaN = true; + bool f1IsInf = true, f1IsMinusInf = true, f1IsNaN = true; + + // if one is not finite, we can avoid the heavy simplification process + _get_infinity_statuses(f1.generator_view_, f2, f1IsInf, f1IsMinusInf, f1IsNaN, f2IsInf, f2IsMinusInf, f2IsNaN); + + if (f1IsNaN || f2IsNaN) return false; + + // inf cases first to avoid costly g1 * g2 check + if constexpr (Co) { + if (f1IsInf) { + if (f2IsInf) return false; + f1 = f2; + return true; + } + if (f1IsMinusInf) { + return false; + } + } else { + if (f1IsMinusInf) { + if (f2IsMinusInf) return false; + f1 = f2; + return true; + } + if (f1IsInf) { + return false; + } + } + + const size_type num_param = f1.num_parameters(); + Multi_parameter_filtration res(num_param, Co ? T_m_inf : T_inf); + std::vector newGen(num_param); + // TODO: see if the order can be used to avoid g1 * g2 add_generator and + // perhaps even to replace add_generator by add_guaranteed_generator + for (size_type g1 = 0; g1 < f1.num_generators(); ++g1) { + for (size_type g2 = 0; g2 < f2.num_generators(); ++g2) { + for (size_type p = 0; p < num_param; ++p) { + if constexpr (Co) { + newGen[p] = std::min(f1(g1, p), f2(g2, p)); + } else { + newGen[p] = std::max(f1(g1, p), f2(g2, p)); + } + } + res.add_generator(newGen); + } + } + swap(f1, res); + + return f1 != res; + } + } + + /** + * @brief Serialize given value into the buffer at given pointer. + * + * @param value Value to serialize. + * @param start Pointer to the start of the space in the buffer where to store the serialization. + * @return End position of the serialization in the buffer. + */ + friend char *serialize_value_to_char_buffer(const Multi_parameter_filtration &value, char *start) + { + const size_type length = value.generators_.size(); + const size_type num_param = value.num_parameters(); + const std::size_t arg_size = sizeof(T) * length; + const std::size_t type_size = sizeof(size_type); + memcpy(start, &num_param, type_size); + memcpy(start + type_size, &length, type_size); + memcpy(start + (type_size * 2), value.generators_.data(), arg_size); + return start + arg_size + (type_size * 2); + } + + /** + * @brief Deserialize the value from a buffer at given pointer and stores it in given value. + * + * @param value Value to fill with the deserialized filtration value. + * @param start Pointer to the start of the space in the buffer where the serialization is stored. + * @return End position of the serialization in the buffer. + */ + friend const char *deserialize_value_from_char_buffer(Multi_parameter_filtration &value, const char *start) + { + const std::size_t type_size = sizeof(size_type); + size_type length; + size_type num_param; + memcpy(&num_param, start, type_size); + memcpy(&length, start + type_size, type_size); + std::size_t arg_size = sizeof(T) * length; + value.generators_.resize(length); + memcpy(value.generators_.data(), start + (type_size * 2), arg_size); + value.generator_view_ = + Viewer(value.generators_.data(), num_param == 0 ? 0 : value.generators_.size() / num_param, num_param); + return start + arg_size + (type_size * 2); + } + + /** + * @brief Returns the serialization size of the given filtration value. + */ + friend std::size_t get_serialization_size_of(const Multi_parameter_filtration &value) + { + return (sizeof(size_type) * 2) + (sizeof(T) * value.num_entries()); + } + + /** + * @brief Plus infinity value of an entry of the filtration value. + */ + constexpr static const T T_inf = MF_T_inf; + + /** + * @brief Minus infinity value of an entry of the filtration value. + */ + constexpr static const T T_m_inf = MF_T_m_inf; + + private: + Underlying_container generators_; /**< Container of the filtration value elements. */ + Viewer generator_view_; /**< Matrix view of the container. Has to be created after generators_. */ + + /** + * @brief Default value of an element in the filtration value. + */ + constexpr static T _get_default_value() { return Co ? T_inf : T_m_inf; } + + /** + * @brief Verifies if @p b is strictly contained in the positive cone originating in `a`. + */ + static bool _strictly_contains(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b) + { + bool isSame = true; + for (auto i = 0U; i < a.extent(1); ++i) { + T a_i, b_i; + if constexpr (Co) { + a_i = b(g_b, i); + b_i = a(g_a, i); + } else { + a_i = a(g_a, i); + b_i = b(g_b, i); + } + if (a_i > b_i || _is_nan(a_i) || _is_nan(b_i)) return false; + if (isSame && a_i != b_i) isSame = false; + } + return !isSame; + } + + /** + * @brief Verifies if @p b is contained in the positive cone originating in `a`. + */ + static bool _contains(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b) + { + for (std::size_t i = 0U; i < a.extent(1); ++i) { + T a_i, b_i; + if constexpr (Co) { + a_i = b(g_b, i); + b_i = a(g_a, i); + } else { + a_i = a(g_a, i); + b_i = b(g_b, i); + } + if (a_i > b_i || (!_is_nan(a_i) && _is_nan(b_i)) || (_is_nan(a_i) && !_is_nan(b_i))) return false; + } + return true; + } + + /** + * @brief Verifies if the first element of @p b strictly dominates the first element of `a`. + */ + static bool _first_strictly_dominates(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b) + { + if constexpr (Co) { + return a(g_a, 0) < b(g_b, 0); + } else { + return a(g_a, 0) > b(g_b, 0); + } + } + + /** + * @brief Verifies if the first element of @p b dominates the first element of `a`. + */ + static bool _first_dominates(const Viewer &a, size_type g_a, const Viewer &b, size_type g_b) + { + if constexpr (Co) { + return a(g_a, 0) <= b(g_b, 0); + } else { + return a(g_a, 0) >= b(g_b, 0); + } + } + + /** + * @brief Applies operation on the elements of the filtration value. + */ + template ::has_begin> > + void _apply_operation(const ValueRange &range, F &&operate) + { + auto &view = generator_view_; + for (unsigned int g = 0; g < num_generators(); ++g) { + auto it = range.begin(); + for (unsigned int p = 0; p < num_parameters() && it != range.end(); ++p) { + std::forward(operate)(view(g, p), *it); + ++it; + } + } + } + + /** + * @brief Applies operation on the elements of the filtration value. + */ + template + void _apply_operation(const T &val, F &&operate) + { + auto &gens = generators_; + for (unsigned int i = 0; i < gens.size(); ++i) { + std::forward(operate)(gens[i], val); + } + } + + template + static void _get_infinity_statuses(const Viewer &a, + const GeneratorRange &b, + bool &aIsInf, + bool &aIsMinusInf, + bool &aIsNaN, + bool &bIsInf, + bool &bIsMinusInf, + bool &bIsNaN) + { + auto itB = b.begin(); + for (std::size_t i = 0; i < a.extent(1); ++i) { + if (a(0, i) != T_inf) aIsInf = false; + if (a(0, i) != T_m_inf) aIsMinusInf = false; + if (!_is_nan(a(0, i))) aIsNaN = false; + if (*itB != T_inf) bIsInf = false; + if (*itB != T_m_inf) bIsMinusInf = false; + if (!_is_nan(*itB)) bIsNaN = false; + if (!aIsInf && !aIsMinusInf && !aIsNaN && !bIsInf && !bIsMinusInf && !bIsNaN) return; + ++itB; + } + } + + enum class Rel : std::uint8_t { EQUAL, DOMINATES, IS_DOMINATED, NONE }; + + template + static Rel _get_domination_relation(const Viewer &a, size_type g_a, Iterator itB) + { + bool equal = true; + bool allGreater = true; + bool allSmaller = true; + bool allNaNA = true; + bool allNaNB = true; + for (unsigned int i = 0; i < a.extent(1); ++i) { + if (a(g_a, i) < *itB) { + if (!allSmaller) return Rel::NONE; + equal = false; + allGreater = false; + } else if (a(g_a, i) > *itB) { + if (!allGreater) return Rel::NONE; + equal = false; + allSmaller = false; + } + if (!_is_nan(a(g_a, i))) allNaNA = false; + if (!_is_nan(*itB)) allNaNB = false; + ++itB; + } + if (allNaNA || allNaNB) return Rel::IS_DOMINATED; + if (equal) return Rel::EQUAL; + + if constexpr (Co) { + if (allSmaller) return Rel::DOMINATES; + return Rel::IS_DOMINATED; + } else { + if (allGreater) return Rel::DOMINATES; + return Rel::IS_DOMINATED; + } + } + + /** + * @brief Verifies how x can be added as a new generator with respect to an already existing generator, represented + * by `indices[curr]`. If x is dominated by or is equal to `indices[curr]`, it cannot be added. If it dominates + * `indices[curr]`, it has to replace `indices[curr]`. If there is no relation between both, `indices[curr]` has + * no influence on the addition of x. + * + * Assumes between 'curr' and 'end' everything is simplified: + * no nan values and if there is an inf/-inf, then 'end - curr == 1'. + */ + template + bool _generator_can_be_added(Iterator x, std::size_t curr, std::size_t &end, std::vector &indices) + { + while (curr != end) { + Rel res = _get_domination_relation(generator_view_, indices[curr], x); + if (res == Rel::IS_DOMINATED || res == Rel::EQUAL) return false; // x dominates or is equal + if (res == Rel::DOMINATES) { // x is dominated + --end; + std::swap(indices[curr], indices[end]); + } else { // no relation + ++curr; + } + } + return true; + } + + /** + * @brief Rebuild the generators from the given set. + */ + template + void _build_from(std::vector &indices, const int newIndex, Iterator xStart, Iterator xEnd) + { + auto comp = [&](int g1, int g2) -> bool { + if (g1 == g2) { + return false; + } + + if (g1 == newIndex) { + auto it = xStart; + for (size_type i = 0; i < num_parameters(); ++i) { + T v = generator_view_(g2, i); + if (*it != v) { + if (_is_nan(v)) return true; + return *it < v; + } + ++it; + } + return false; + } + + if (g2 == newIndex) { + auto it = xStart; + for (size_type i = 0; i < num_parameters(); ++i) { + T v = generator_view_(g1, i); + if (v != *it) { + if (_is_nan(*it)) return true; + return v < *it; + } + ++it; + } + return false; + } + + for (size_type i = 0; i < num_parameters(); ++i) { + T v1 = generator_view_(g1, i); + T v2 = generator_view_(g2, i); + if (v1 != v2) { + if (_is_nan(v2)) return true; + return v1 < v2; + } + } + return false; + }; + std::sort(indices.begin(), indices.end(), comp); + + Underlying_container new_container; + new_container.reserve((indices.size() + 1) * num_parameters()); + for (int i : indices) { + if (i == newIndex) { + new_container.insert(new_container.end(), xStart, xEnd); + } else { + T *ptr = &generator_view_(i, 0); + for (size_type p = 0; p < num_parameters(); ++p) { + new_container.push_back(*ptr); + ++ptr; + } + } + } + generators_.swap(new_container); + generator_view_ = Viewer(generators_.data(), generators_.size() / num_parameters(), num_parameters()); + } + + /** + * @brief Rebuild the generators from the given set. + */ + void _build_from(std::vector &indices) + { + auto comp = [&](int g1, int g2) -> bool { + for (std::size_t i = 0; i < num_parameters(); ++i) { + T v1 = generator_view_(g1, i); + T v2 = generator_view_(g2, i); + if (v1 != v2) { + if (_is_nan(v2)) return true; + return v1 < v2; + } + } + return false; + }; + std::sort(indices.begin(), indices.end(), comp); + + Underlying_container new_container; + new_container.reserve(indices.size() * num_parameters()); + for (int i : indices) { + T *ptr = &generator_view_(i, 0); + for (size_type p = 0; p < num_parameters(); ++p) { + new_container.push_back(*ptr); + ++ptr; + } + } + generators_.swap(new_container); + generator_view_ = Viewer(generators_.data(), generators_.size() / num_parameters(), num_parameters()); + } + + bool _is_finite(size_type g) + { + bool isInf = true, isMinusInf = true, isNan = true; + for (size_type p = 0; p < num_parameters(); ++p) { + T v = generator_view_(g, p); + if (v != T_inf) isInf = false; + if (v != T_m_inf) isMinusInf = false; + if (!_is_nan(v)) isNan = false; + if (!isInf && !isMinusInf && !isNan) return true; + } + return false; + } + + template + static U _compute_frobenius_norm(size_type number_of_elements, F &&norm) + { + if (number_of_elements == 1) return std::forward(norm)(0); + + U out = 0; + for (size_type p = 0; p < number_of_elements; ++p) { + T v = std::forward(norm)(p); + out += v * v; + } + if constexpr (std::is_integral_v) { + // to avoid Windows issue that don't know how to cast integers for cmath methods + return std::sqrt(static_cast(out)); + } else { + return std::sqrt(out); + } + } +}; + +} // namespace Gudhi::multi_filtration + +namespace std { + +template +class numeric_limits > +{ + public: + using Filtration_value = Gudhi::multi_filtration::Multi_parameter_filtration; + + static constexpr bool has_infinity = true; + static constexpr bool has_quiet_NaN = std::numeric_limits::has_quiet_NaN; + + static constexpr Filtration_value infinity(std::size_t p = 1) noexcept { return Filtration_value::inf(p); }; + + // non-standard + static constexpr Filtration_value minus_infinity(std::size_t p = 1) noexcept + { + return Filtration_value::minus_inf(p); + }; + + static constexpr Filtration_value max() noexcept(false) + { + throw std::logic_error( + "The max value cannot be represented with no finite numbers of parameters." + "Use `max(number_of_parameters)` instead"); + }; + + static constexpr Filtration_value max(std::size_t p) noexcept + { + return Filtration_value(p, std::numeric_limits::max()); + }; + + static constexpr Filtration_value lowest(std::size_t p = 1) noexcept { return Filtration_value::minus_inf(p); }; + + static constexpr Filtration_value quiet_NaN(std::size_t p = 1) noexcept(false) + { + if constexpr (std::numeric_limits::has_quiet_NaN) { + return Filtration_value::nan(p); + } else { + throw std::logic_error("Does not have a NaN value."); + } + }; +}; + +} // namespace std + +#endif // MF_MULTI_PARAMETER_FILTRATION_H_ diff --git a/multipers/gudhi/gudhi/Multi_persistence/Box.h b/multipers/gudhi/gudhi/Multi_persistence/Box.h index 5bc1d13d..a300cd96 100644 --- a/multipers/gudhi/gudhi/Multi_persistence/Box.h +++ b/multipers/gudhi/gudhi/Multi_persistence/Box.h @@ -5,7 +5,8 @@ * Copyright (C) 2023 Inria * * Modification(s): - * - 2024/08 Hannah Schreiber: doc + * - 2024/08 Hannah Schreiber: documentation + * - 2025/03 Hannah Schreiber: Change of point types. * - YYYY/MM Author: Description of the modification */ @@ -15,15 +16,16 @@ * @brief Contains the @ref Gudhi::multi_persistence::Box class. */ -#ifndef BOX_H_INCLUDED -#define BOX_H_INCLUDED +#ifndef MP_BOX_H_INCLUDED +#define MP_BOX_H_INCLUDED #include //std::ostream #include -#include +#include +#include -namespace Gudhi{ +namespace Gudhi { namespace multi_persistence { /** @@ -32,18 +34,17 @@ namespace multi_persistence { * * @brief Simple box in \f$\mathbb R^n\f$ defined by two diametrically opposite corners. * - * @tparam T Type of the coordinates of the Box. Has to follow the conditions of the template parameter of - * @ref One_critical_filtration "". + * @tparam T Type of the coordinates of the Box. */ template class Box { public: - using Point = Gudhi::multi_filtration::One_critical_filtration; /**< Type of a point in \f$\mathbb R^n\f$. */ + using Point_t = Point; /**< Type of a point in \f$\mathbb R^n\f$. */ /** * @brief Default constructor. Constructs a trivial box with corners at minus infinity. */ - Box() {} + Box() = default; /** * @brief Constructs a box from the two given corners. Assumes that \f$ lowerCorner \le @p upperCorner \f$ and @@ -52,9 +53,11 @@ class Box { * @param lowerCorner First corner of the box. Has to be smaller than `upperCorner`. * @param upperCorner Second corner of the box. Has to be greater than `lowerCorner`. */ - Box(const Point &lowerCorner, const Point &upperCorner) : lowerCorner_(lowerCorner), upperCorner_(upperCorner) { - GUDHI_CHECK(lowerCorner.size() == upperCorner.size() || !lowerCorner.is_finite() || !upperCorner.is_finite(), - "The two corners of the box don't have the same dimension."); + Box(const Point_t &lowerCorner, const Point_t &upperCorner) : lowerCorner_(lowerCorner), upperCorner_(upperCorner) { + GUDHI_CHECK(lowerCorner.size() == upperCorner.size(), + std::invalid_argument("The two corners of the box don't have the same dimension.")); + // GUDHI_CHECK(lowerCorner <= upperCorner, std::invalid_argument("The first corner is not smaller than the + // second.")); } /** @@ -63,82 +66,93 @@ class Box { * * @param box Pair of corners defining the wished box. */ - Box(const std::pair &box) : Box(box.first, box.second) {} + Box(const std::pair &box) : Box(box.first, box.second) {} /** * @brief Returns the lowest of both defining corners. */ - const Point &get_lower_corner() const { return lowerCorner_; } + const Point_t &get_lower_corner() const { return lowerCorner_; } /** * @brief Returns the lowest of both defining corners. */ - Point &get_lower_corner() { return lowerCorner_; } + Point_t &get_lower_corner() { return lowerCorner_; } /** * @brief Returns the greatest of both defining corners. */ - Point &get_upper_corner() { return upperCorner_; } + Point_t &get_upper_corner() { return upperCorner_; } /** * @brief Returns the greatest of both defining corners. */ - const Point &get_upper_corner() const { return upperCorner_; } + const Point_t &get_upper_corner() const { return upperCorner_; } /** * @brief Returns a pair of const references to both defining corners. */ - std::pair get_bounding_corners() const { return {lowerCorner_, upperCorner_}; } + std::pair get_bounding_corners() const { return {lowerCorner_, upperCorner_}; } /** * @brief Returns a pair of references to both defining corners. */ - std::pair get_bounding_corners() { return {lowerCorner_, upperCorner_}; } + std::pair get_bounding_corners() { return {lowerCorner_, upperCorner_}; } /** * @brief Returns true if and only if one of the following is true: * - one of the corners is empty - * - one of the corners has value NaN + * - one of the corners contains the value NaN * - both corners have value infinity * - both corners have value minus infinity - * - both corners are finite but don't have the same dimension. - */ - bool is_trivial() const { - return lowerCorner_.empty() || upperCorner_.empty() || lowerCorner_.is_nan() || upperCorner_.is_nan() || - (lowerCorner_.is_plus_inf() && upperCorner_.is_plus_inf()) || - (lowerCorner_.is_minus_inf() && upperCorner_.is_minus_inf()) || - (lowerCorner_.is_finite() && upperCorner_.is_finite() && - lowerCorner_.num_parameters() != upperCorner_.num_parameters()); + * + * Throws if both corners don't have the same dimension. + */ + [[nodiscard]] bool is_trivial() const { + if (lowerCorner_.size() == 0 || upperCorner_.size() == 0) return true; + if (lowerCorner_.size() != upperCorner_.size()) + throw std::logic_error("Upper and lower corner do not have the same dimension"); + + T inf = Point_t::T_inf; + T m_inf = Point_t::T_m_inf; + + bool lowerIsInf = true, lowerIsMinusInf = true; + bool upperIsInf = true, upperIsMinusInf = true; + for (unsigned int i = 0; i < lowerCorner_.size(); ++i) { + T lc = lowerCorner_[i]; + T uc = upperCorner_[i]; + if (Gudhi::multi_filtration::_is_nan(lc) || Gudhi::multi_filtration::_is_nan(uc)) return true; + if (lc != inf) lowerIsInf = false; + if (lc != m_inf) lowerIsMinusInf = false; + if (uc != inf) upperIsInf = false; + if (uc != m_inf) upperIsMinusInf = false; + if ((!lowerIsInf && !lowerIsMinusInf) || (!upperIsInf && !upperIsMinusInf)) return false; + } + return (lowerIsInf && upperIsInf) || (lowerIsMinusInf && upperIsMinusInf); } /** * @brief Returns true if and only if the given point is inside the box. - * If the box is not {-infinity, infinity} and the given point is finite, but has not the same dimension - * than the box, the point is considered outside. - */ - bool contains(const Point &point) const { - if (point.is_nan() || is_trivial()) return false; - if (point.is_plus_inf()) return upperCorner_.is_plus_inf(); - if (point.is_minus_inf()) return lowerCorner_.is_minus_inf(); - - if ((lowerCorner_.is_finite() && point.size() != lowerCorner_.size()) || - (upperCorner_.is_finite() && point.size() != upperCorner_.size())) { - // TODO: make it a warning, with future GUDHI_CHECK version? - // std::cerr << "Box and point are not of the same dimension." << std::endl; - return false; + */ + bool contains(const Point_t &point) const { + GUDHI_CHECK(point.size() == lowerCorner_.size(), + std::invalid_argument("Point should not have a different dimension than the box.")); + + for (unsigned int i = 0; i < point.size(); ++i) { + T lc = lowerCorner_[i]; + T uc = upperCorner_[i]; + T p = point[i]; + if (Gudhi::multi_filtration::_is_nan(p) || Gudhi::multi_filtration::_is_nan(lc) || + Gudhi::multi_filtration::_is_nan(uc)) + return false; + if (lc > p || uc < p) return false; } - - return lowerCorner_ <= point && point <= upperCorner_; + return true; } /** - * @brief Returns the dimension of the box. If the box is trivial or both corners are infinite, the dimension is 0. + * @brief Returns the dimension of the box. */ - std::size_t dimension() const { - if (is_trivial()) return 0; - if (lowerCorner_.is_minus_inf() && upperCorner_.is_plus_inf()) return 0; // not so sure what we want to do here - return lowerCorner_.is_finite() ? lowerCorner_.size() : upperCorner_.size(); - } + [[nodiscard]] std::size_t dimension() const { return lowerCorner_.size(); } /** * @brief Inflates the box by delta. @@ -149,14 +163,23 @@ class Box { lowerCorner_ -= delta; upperCorner_ += delta; } - friend bool operator==(const Box& a, const Box&b){ + + /** + * @brief Equality operator. Two boxes are equal if and only if both defining corners are equal. + */ + friend bool operator==(const Box &a, const Box &b) { return a.upperCorner_ == b.upperCorner_ && a.lowerCorner_ == b.lowerCorner_; } + /** + * @brief Unequality operator. Two boxes are equal if and only if both defining corners are equal. + */ + friend bool operator!=(const Box &a, const Box &b) { return !(a == b); } + /** * @brief Outstream operator. */ - friend std::ostream &operator<<(std::ostream &os, const Box &box) { + friend std::ostream &operator<<(std::ostream &os, const Box &box) { os << "Box -- Bottom corner : "; os << box.get_lower_corner(); os << ", Top corner : "; @@ -164,11 +187,26 @@ class Box { return os; } + template + friend Box smallest_enclosing_box(const Box &a, const Box &b); + private: - Point lowerCorner_; /**< Lowest of defining corners. */ - Point upperCorner_; /**< Greatest of defining corners. */ + Point_t lowerCorner_; /**< Lowest of defining corners. */ + Point_t upperCorner_; /**< Greatest of defining corners. */ }; -}} // namespace Gudhi::multi_persistence +template +Box smallest_enclosing_box(const Box &a, const Box &b) { + Box box; + auto &lower = box.get_lower_corner(); + auto &upper = box.get_upper_corner(); + for (unsigned int i = 0; i < a.dimension(); ++i) { + lower[i] = std::min(a.get_lower_corner()[i], b.get_lower_corner()[i]); + upper[i] = std::max(a.get_upper_corner()[i], b.get_upper_corner()[i]); + } + return box; +} +} // namespace multi_persistence +} // namespace Gudhi -#endif // BOX_H_INCLUDED +#endif // MP_BOX_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_persistence/Line.h b/multipers/gudhi/gudhi/Multi_persistence/Line.h index cb65ee50..182a0698 100644 --- a/multipers/gudhi/gudhi/Multi_persistence/Line.h +++ b/multipers/gudhi/gudhi/Multi_persistence/Line.h @@ -5,7 +5,8 @@ * Copyright (C) 2023 Inria * * Modification(s): - * - 2024/08 Hannah Schreiber: doc + * - 2024/08 Hannah Schreiber: documentation + * - 2025/03 Hannah Schreiber: Change of point types. * - YYYY/MM Author: Description of the modification */ @@ -15,20 +16,19 @@ * @brief Contains the @ref Gudhi::multi_persistence::Line class. */ -#ifndef LINE_FILTRATION_TRANSLATION_H_INCLUDED -#define LINE_FILTRATION_TRANSLATION_H_INCLUDED +#ifndef MP_LINE_FILTRATION_H_INCLUDED +#define MP_LINE_FILTRATION_H_INCLUDED #include #include #include -#include #include -#include -#include #include +#include +#include -namespace Gudhi{ +namespace Gudhi { namespace multi_persistence { /** @@ -37,37 +37,36 @@ namespace multi_persistence { * * @brief A line in \f$\mathbb R^n\f$, with some helpers to project points on it. * - * @tparam T Type of the coordinate values. Has to follow the conditions of the template parameter of - * @ref One_critical_filtration "". + * @tparam T Type of the coordinate values. */ template -class Line { +class Line +{ public: /** * @brief Coordinates in \f$\mathbb R^n\f$. */ - using Point = Gudhi::multi_filtration::One_critical_filtration; - /** - * @brief Set of coordinates in \f$\mathbb R^n\f$. - */ - using K_critical_point = Gudhi::multi_filtration::Multi_critical_filtration; + using Point_t = Point; /** * @brief Default constructor. Sets the number of coordinates to 0. */ - Line() : basePoint_(0), direction_(0) {} // has to be explicitly set to 0, otherwise becomes -inf + Line() = default; + /** * @brief Constructs a line going through the given point with slope 1. * * @param x A point of the line. */ - Line(const Point &x) : basePoint_(x), direction_(0) {} // default direction + Line(const Point_t &x) : basePoint_(x), direction_() {} // default direction + /** * @brief Constructs a line going through the given point with slope 1. * * @param x A point of the line. Will be moved. */ - Line(Point &&x) : basePoint_(std::move(x)), direction_(0) {} // default direction + Line(Point_t &&x) : basePoint_(std::move(x)), direction_() {} // default direction + /** * @brief Constructs a line going through the given point in the direction of the given vector. * If the vector has no coordinates, the slope is assumed to be 1. @@ -76,24 +75,22 @@ class Line { * @param x A point of the line. * @param vector Direction of the line. Positive and non trivial. */ - Line(const Point &x, const Point &vector) : basePoint_(x), direction_(vector) { check_direction_(); } + Line(const Point_t &x, const Point_t &vector) : basePoint_(x), direction_(vector) { _check_direction(); } /** * @brief Returns the coordinates of the point on the line with "time" parameter `t`. That is, the point \f$ x \f$ * such that \f$ x[i] = base\_point[i] + t \times direction[i] \f$ for all \f$ i \in [0, n - 1] \f$ with \f$ n \f$ * the number of coordinates. */ - Point operator[](T t) const { - GUDHI_CHECK(direction_.empty() || direction_.size() == basePoint_.size(), + Point_t operator[](T t) const + { + GUDHI_CHECK(direction_.size() == 0 || direction_.size() == basePoint_.size(), "Direction and base point do not have the same dimension."); - if constexpr (std::numeric_limits::has_quiet_NaN){ //to avoid windows error - if (std::isnan(t)) return Point::nan(); - } - if (t == Point::T_inf) return Point::inf(); - if (t == -Point::T_inf) return Point::minus_inf(); + if (Gudhi::multi_filtration::_is_nan(t) || t == Point_t::T_inf || t == Point_t::T_m_inf) + return Point_t(basePoint_.size(), t); - Point x(basePoint_.size()); + Point_t x(basePoint_.size()); if (direction_.size() > 0) { for (std::size_t i = 0; i < x.size(); i++) x[i] = basePoint_[i] + t * direction_[i]; @@ -106,7 +103,8 @@ class Line { /** * @brief Translates the given line in the given direction. */ - friend Line &operator+=(Line &to_translate, const Point &v) { + friend Line &operator+=(Line &to_translate, const Point_t &v) + { to_translate.basePoint_ += v; return to_translate; } @@ -114,50 +112,46 @@ class Line { /** * @brief Returns a reference to the current base point of the line. */ - Point &base_point() { return basePoint_; } + Point_t &base_point() { return basePoint_; } + /** * @brief Returns a const reference to the current base point of the line. */ - const Point &base_point() const { return basePoint_; } + const Point_t &base_point() const { return basePoint_; } /** * @brief Returns a reference to the direction vector of the line. */ - Point &direction() { return direction_; } + Point_t &direction() { return direction_; } + /** * @brief Returns a const reference to the direction vector of the line. */ - const Point &direction() const { return direction_; } - - // TODO: factorize forward and backward version by adding a `co` to One_critical_filtration? - // Could make problems with One_critical_filtration being the type of basePoint_ and direction_ + const Point_t &direction() const { return direction_; } /** * @brief Computes the "time" parameter \f$ t \f$ of the starting point \f$ p = base\_point + t \times direction \f$ - * of the intersection between the line and the closed positive cone originating at `x`. + * of the intersection between the line and the closed positive cone originating at point `x`. * * @tparam U Type of the time parameter. * @param x Origin of the closed positive cone. */ template - U compute_forward_intersection(const Point &x) const { - GUDHI_CHECK(direction_.empty() || direction_.size() == x.size(), "x has not as many parameters as the line."); - - constexpr const U inf = - std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); - if (x.is_plus_inf() || x.is_nan()) return inf; - if (x.is_minus_inf()) return -inf; - U t = -inf; - if (direction_.size()) { - for (std::size_t i = 0; i < x.size(); i++) { - if (direction_[i] == 0) { - if (x[i] > basePoint_[i]) return inf; - } else { - t = std::max(t, (static_cast(x[i]) - static_cast(basePoint_[i])) / static_cast((direction_[i]))); - } + U compute_forward_intersection(const Point_t &x) const + { + GUDHI_CHECK(basePoint_.size() == x.size(), "x has not as many parameters as the line."); + + constexpr const U inf = Point::T_inf; + + U t = Point::T_m_inf; + for (unsigned int p = 0; p < x.size(); ++p) { + if (Gudhi::multi_filtration::_is_nan(x[p])) return inf; + auto div = direction_.size() == 0 ? 1 : direction_[p]; + if (div == 0) { + if (x[p] > basePoint_[p]) return inf; + } else { + t = std::max(t, (static_cast(x[p]) - static_cast(basePoint_[p])) / static_cast(div)); } - } else { - for (std::size_t i = 0; i < x.size(); i++) t = std::max(t, static_cast(x[i]) - static_cast(basePoint_[i])); } return t; @@ -165,70 +159,100 @@ class Line { /** * @brief Computes the "time" parameter \f$ t \f$ of the starting point \f$ p = base\_point + t \times direction \f$ - * of the intersection between the line and the union of closed positive cones originating at the points in `x`. + * of the intersection between the line and the union of closed positive cones originating at + * multi-parameter filtration value `x`. If `x` contains a NaN value, returns +infinity. * * @tparam U Type of the time parameter. - * @param x Set of origins for the closed positive cones. + * @tparam FiltrationValue Type of a multi-parameter filtration value. Has to implement the following methods: + * `num_parameters`, `num_generators`, `operator()(generator_index, parameter_index)`. + * See @ref Gudhi::multi_filtration::Multi_parameter_filtration for an example. + * @param x Origin of the closed positive cones. */ - template - U compute_forward_intersection(const K_critical_point &x) const { - constexpr const U inf = - std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); - if (x.is_plus_inf() || x.is_nan()) return inf; - if (x.is_minus_inf()) return -inf; + template + U compute_forward_intersection(const FiltrationValue &x) const + { + GUDHI_CHECK(basePoint_.size() == x.num_parameters(), "x has not as many parameters as the line."); + + constexpr const U inf = Point::T_inf; + U t = inf; - for (const auto &y : x) { - t = std::min(t, compute_forward_intersection(y)); + for (unsigned int g = 0; g < x.num_generators(); ++g) { + U tmp = Point::T_m_inf; + for (unsigned int p = 0; p < x.num_parameters(); ++p) { + if (Gudhi::multi_filtration::_is_nan(x(g, p))) return inf; + auto div = direction_.size() == 0 ? 1 : direction_[p]; + if (div == 0) { + if (x(g, p) > basePoint_[p]) tmp = inf; + } else { + tmp = std::max(tmp, (static_cast(x(g, p)) - static_cast(basePoint_[p])) / static_cast(div)); + } + } + t = std::min(t, tmp); } + return t; } /** * @brief Computes the "time" parameter \f$ t \f$ of the starting point \f$ p = base\_point + t \times direction \f$ - * of the intersection between the line and the open negative cone originating at `x`. + * of the intersection between the line and the open negative cone originating at point `x`. * * @tparam U Type of the time parameter. * @param x Origin of the open negative cone. */ template - U compute_backward_intersection(const Point &x) const { - constexpr const U inf = - std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); - if (x.is_plus_inf()) return inf; - if (x.is_minus_inf() || x.is_nan()) return -inf; - U t = inf; - - if (direction_.size()) { - for (std::size_t i = 0; i < x.size(); i++) { - if (direction_[i] == 0) { - if (x[i] <= basePoint_[i]) return -inf; - } else { - t = std::min(t, (static_cast(x[i]) - static_cast(basePoint_[i])) / static_cast(direction_[i])); - } + U compute_backward_intersection(const Point_t &x) const + { + GUDHI_CHECK(basePoint_.size() == x.size(), "x has not as many parameters as the line."); + + constexpr const U m_inf = Point::T_m_inf; + + U t = Point::T_inf; + for (unsigned int p = 0; p < x.size(); ++p) { + if (Gudhi::multi_filtration::_is_nan(x[p])) return m_inf; + auto div = direction_.size() == 0 ? 1 : direction_[p]; + if (div == 0) { + if (x[p] <= basePoint_[p]) return m_inf; + } else { + t = std::min(t, (static_cast(x[p]) - static_cast(basePoint_[p])) / static_cast(div)); } - } else { - for (std::size_t i = 0; i < x.size(); i++) t = std::min(t, static_cast(x[i] - basePoint_[i])); } + return t; } /** * @brief Computes the "time" parameter \f$ t \f$ of the starting point \f$ p = base\_point + t \times direction \f$ - * of the intersection between the line and the union of open negative cones originating at the points in `x`. + * of the intersection between the line and the union of open negative cones originating at + * multi-parameter filtration value `x`. If `x` contains a NaN value, returns -infinity. * * @tparam U Type of the time parameter. - * @param x Set of origins for the open negative cones. + * @tparam FiltrationValue Type of a multi-parameter filtration value. Has to implement the following methods: + * `num_parameters`, `num_generators`, `operator()(generator_index, parameter_index)`. + * @param x Origin of the open negative cones. */ - template - U compute_backward_intersection(const K_critical_point &x) const { - constexpr const U inf = - std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); - if (x.is_plus_inf()) return inf; - if (x.is_minus_inf() || x.is_nan()) return -inf; - U t = -inf; - for (const auto &y : x) { - t = std::max(t, compute_backward_intersection(y)); + template + U compute_backward_intersection(const FiltrationValue &x) const + { + GUDHI_CHECK(basePoint_.size() == x.num_parameters(), "x has not as many parameters as the line."); + + constexpr const U m_inf = Point::T_m_inf; + + U t = m_inf; + for (unsigned int g = 0; g < x.num_generators(); ++g) { + U tmp = Point::T_inf; + for (unsigned int p = 0; p < x.num_parameters(); ++p) { + if (Gudhi::multi_filtration::_is_nan(x(g, p))) return m_inf; + auto div = direction_.size() == 0 ? 1 : direction_[p]; + if (div == 0) { + if (x(g, p) <= basePoint_[p]) tmp = m_inf; + } else { + tmp = std::min(tmp, (static_cast(x(g, p)) - static_cast(basePoint_[p])) / static_cast(div)); + } + } + t = std::max(t, tmp); } + return t; } @@ -239,25 +263,27 @@ class Line { * @return A pair representing the two bounding points of the intersection, such that the first element is the * smallest of the two. If the box and the line do not intersect or the box is trivial, returns the pair {inf, -inf}. */ - std::pair get_bounds(const Box &box) const { - if (box.is_trivial()) return {Point::T_inf, -Point::T_inf}; + std::pair get_bounds(const Box &box) const + { + if (box.is_trivial()) return {Point_t::T_inf, Point_t::T_m_inf}; T bottom = compute_forward_intersection(box.get_lower_corner()); T top = compute_backward_intersection(box.get_upper_corner()); - if (bottom > top) return {Point::T_inf, -Point::T_inf}; // no intersection + if (bottom > top) return {Point_t::T_inf, Point_t::T_m_inf}; // no intersection return {bottom, top}; } private: - Point basePoint_; /**< Any point on the line. */ - Point direction_; /**< Direction of the line. */ + Point_t basePoint_; /**< Any point on the line. */ + Point_t direction_; /**< Direction of the line. */ /** * @brief Checks that the arguments define a correct and positively slopped line. */ - void check_direction_() const { + void _check_direction() const + { if (direction_.size() == 0) return; // default slope bool is_trivial = true; @@ -277,6 +303,7 @@ class Line { } }; -}} // namespace Gudhi::multi_persistence +} // namespace multi_persistence +} // namespace Gudhi -#endif // LINE_FILTRATION_TRANSLATION_H_INCLUDED +#endif // MP_LINE_FILTRATION_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h b/multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h new file mode 100644 index 00000000..0cd1d09c --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h @@ -0,0 +1,268 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_parameter_filtered_complex_pcoh_interface.h + * @author Hannah Schreiber + * @brief Contains the @ref Gudhi::multi_persistence::Multi_parameter_filtered_complex_pcoh_interface class. + */ + +#ifndef MP_COMPLEX_PCOH_INTERFACE_H_INCLUDED +#define MP_COMPLEX_PCOH_INTERFACE_H_INCLUDED + +#include +#include + +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Multi_parameter_filtered_complex_pcoh_interface Multi_parameter_filtered_complex_pcoh_interface.h \ + * gudhi/Multi_persistence/Multi_parameter_filtered_complex_pcoh_interface.h + * @ingroup multi_persistence + * + * @brief Interface respecting the @ref FilteredComplex concept to use + * @ref Gudhi::persistent_cohomology::Persistent_cohomology with an instantiation of + * @ref Multi_parameter_filtered_complex. + * + * @tparam MultiFiltrationValue Filtration value type used in @ref Multi_parameter_filtered_complex. + */ +template +class Multi_parameter_filtered_complex_pcoh_interface +{ + public: + using Complex = Multi_parameter_filtered_complex; /**< Complex type */ + using Simplex_key = std::uint32_t; /**< Simplex_key type */ + using Simplex_handle = Simplex_key; /**< Simplex_handle type */ + using Filtration_value = Simplex_key; /**< Internal filtration value type */ + using Dimension = int; /**< Internal dimension type */ + using Map = std::vector; /**< Map type */ + using Filtration_simplex_range = Map; /**< Filtration_simplex_range type */ + using Boundary_simplex_range = Map; /**< Boundary_simplex_range type */ + + /** + * @brief Default constructor, storing null pointers.Can be tested with @ref is_initialized(), but any other method + * should not be used with this constructor. + */ + Multi_parameter_filtered_complex_pcoh_interface() : boundaries_(nullptr), newToOldPerm_(nullptr) {} + + /** + * @brief Constructs the interface and stores a pointer to the given complex and the given permutation. Assumes that + * the pointers remain valid through the whole life of the instance. If they get invalidated, they can be updated + * via the copy/move constructors if the content did not change (otherwise, reconstruct from scratch with + * @ref reinitialize). + * + * @param boundaries Complex to interface. + * @param permutation Permutation map indicating the order of the cells stored in @p boundaries as a standard + * 1-dimensional filtration. I.e., `permutation[i]` corresponds to the index in `boundaries` which should be + * used as the i-th cell in the filtration. + */ + Multi_parameter_filtered_complex_pcoh_interface(const Complex &boundaries, const Map &permutation) + : boundaries_(&boundaries), newToOldPerm_(&permutation), keys_(boundaries.get_number_of_cycle_generators(), -1) + {} + + Multi_parameter_filtered_complex_pcoh_interface(const Multi_parameter_filtered_complex_pcoh_interface &other) = + delete; + + /** + * @brief Copy constructor. Copies the complex pointer from @p other and updates the permutation pointer with the + * given map if the complex was initialized. + * + * @param other Interface to copy. + * @param permutation Permutation map. Has to correspond to the same map than in @p other, except that its address + * does not have to be the same. + */ + Multi_parameter_filtered_complex_pcoh_interface(const Multi_parameter_filtered_complex_pcoh_interface &other, + const Map &permutation) + : boundaries_(other.boundaries_), + newToOldPerm_(boundaries_ != nullptr ? &permutation : nullptr), + keys_(other.keys_) + { + GUDHI_CHECK(!other.is_initialized() || permutation == *other.newToOldPerm_, + "Only the address of the permutation vector is allowed to change, not its content."); + } + + /** + * @brief Copy constructor. Updates the pointers with the given complex and map if @p other was initialized. + * + * @param other Interface to copy. + * @param boundaries Complex. Has to correspond to the same complex than in @p other, except that its address + * does not have to be the same. + * @param permutation Permutation map. Has to correspond to the same map than in @p other, except that its address + * does not have to be the same. + */ + Multi_parameter_filtered_complex_pcoh_interface(const Multi_parameter_filtered_complex_pcoh_interface &other, + const Complex &boundaries, + const Map &permutation) + : boundaries_(other.is_initialized() ? &boundaries : nullptr), + newToOldPerm_(other.is_initialized() ? &permutation : nullptr), + keys_(other.keys_) + { + GUDHI_CHECK(!other.is_initialized() || permutation == *other.newToOldPerm_, + "Only the address of the permutation vector is allowed to change, not its content."); + GUDHI_CHECK(!other.is_initialized() || boundaries.get_boundaries() == *other.boundaries_->get_boundaries(), + "Only the address of the complex is allowed to change, not its content."); + } + + Multi_parameter_filtered_complex_pcoh_interface(Multi_parameter_filtered_complex_pcoh_interface &&other) = delete; + + /** + * @brief Move constructor. Moves the complex pointer from @p other and updates the permutation pointer with the + * given map if the complex was initialized. + * + * @param other Interface to move. + * @param permutation Permutation map. Has to correspond to the same map than in @p other, except that its address + * does not have to be the same. + */ + Multi_parameter_filtered_complex_pcoh_interface(Multi_parameter_filtered_complex_pcoh_interface &&other, + const Map &permutation) + : boundaries_(std::exchange(other.boundaries_, nullptr)), + newToOldPerm_(boundaries_ != nullptr ? &permutation : nullptr), + keys_(std::move(other.keys_)) + { + other.newToOldPerm_ = nullptr; + } + + /** + * @brief Move constructor. Updates the pointers with the given complex and map if @p other was initialized. + * + * @param other Interface to move. + * @param boundaries Complex. Has to correspond to the same complex than in @p other, except that its address + * does not have to be the same. + * @param permutation Permutation map. Has to correspond to the same map than in @p other, except that its address + * does not have to be the same. + */ + Multi_parameter_filtered_complex_pcoh_interface(Multi_parameter_filtered_complex_pcoh_interface &&other, + const Complex &boundaries, + const Map &permutation) + : boundaries_(other.is_initialized() ? &boundaries : nullptr), + newToOldPerm_(other.is_initialized() ? &permutation : nullptr), + keys_(std::move(other.keys_)) + { + other.boundaries_ = nullptr; + other.newToOldPerm_ = nullptr; + } + + ~Multi_parameter_filtered_complex_pcoh_interface() = default; + + Multi_parameter_filtered_complex_pcoh_interface &operator=( + const Multi_parameter_filtered_complex_pcoh_interface &other) = delete; + Multi_parameter_filtered_complex_pcoh_interface &operator=( + Multi_parameter_filtered_complex_pcoh_interface &&other) noexcept = delete; + + /** + * @brief Swap operator. + */ + friend void swap(Multi_parameter_filtered_complex_pcoh_interface &be1, + Multi_parameter_filtered_complex_pcoh_interface &be2) noexcept + { + std::swap(be1.boundaries_, be2.boundaries_); + std::swap(be1.newToOldPerm_, be2.newToOldPerm_); + be1.keys_.swap(be2.keys_); + } + + /** + * @brief Reinitializes the interface with the new given complex and permutation. + * To use instead of the classical assign operator `operator=`. + */ + void reinitialize(const Complex &boundaries, const Map &permutation) + { + boundaries_ = &boundaries; + newToOldPerm_ = &permutation; + keys_ = Map(boundaries.get_number_of_cycle_generators(), -1); + } + + void reset() + { + boundaries_ = nullptr; + newToOldPerm_ = nullptr; + keys_.clear(); + } + + /** + * @brief Returns `true` if and only if all pointers are not null. + */ + [[nodiscard]] bool is_initialized() const { return boundaries_ != nullptr && newToOldPerm_ != nullptr; } + + [[nodiscard]] std::size_t num_simplices() const { return newToOldPerm_->size(); } + + [[nodiscard]] Filtration_value filtration(Simplex_handle sh) const + { + return sh == null_simplex() ? std::numeric_limits::max() : keys_[sh]; + } + + [[nodiscard]] Dimension dimension() const { return boundaries_->get_max_dimension(); } + + [[nodiscard]] Dimension dimension(Simplex_handle sh) const + { + return sh == null_simplex() ? -1 : boundaries_->get_dimensions()[sh]; + } + + // assumes that pcoh will assign the keys from 0 to n in order of filtrations + void assign_key(Simplex_handle sh, Simplex_key key) + { + if (sh != null_simplex()) keys_[sh] = key; + } + + [[nodiscard]] Simplex_key key(Simplex_handle sh) const { return sh == null_simplex() ? null_key() : keys_[sh]; } + + static constexpr Simplex_key null_key() { return static_cast(-1); } + + [[nodiscard]] Simplex_handle simplex(Simplex_key key) const + { + return key == null_key() ? null_simplex() : (*newToOldPerm_)[key]; + } + + static constexpr Simplex_handle null_simplex() { return static_cast(-1); } + + // only used in update_cohomology_groups_edge, so not used without optimizations + [[nodiscard]] std::pair endpoints(Simplex_handle sh) const + { + if (sh == null_simplex()) return {null_simplex(), null_simplex()}; + GUDHI_CHECK(dimension(sh) == 1, "Endpoints only available for edges."); + const auto &col = boundary_simplex_range(sh); + GUDHI_CHECK(col.size() == 2, "Edge should have two vertices as border."); + return {col[0], col[1]}; + } + + [[nodiscard]] const Filtration_simplex_range &filtration_simplex_range() const { return *newToOldPerm_; } + + [[nodiscard]] const Boundary_simplex_range &boundary_simplex_range(Simplex_handle sh) const + { + return boundaries_->get_boundaries()[sh]; + } + + friend std::ostream &operator<<(std::ostream &stream, + const Multi_parameter_filtered_complex_pcoh_interface &complex) + { + stream << "[\n"; + for (auto i : complex.filtration_simplex_range()) { + stream << "["; + for (const auto &idx : complex.boundary_simplex_range(i)) stream << complex.keys_[idx] << ", "; + stream << "]\n"; + } + + stream << "]\n"; + return stream; + } + + private: + Complex const *boundaries_; /**< Pointer to complex. */ + Map const *newToOldPerm_; /**< Pointer to filtration position to complex position map. */ + Map keys_; /**< Keys assigned to a cell. TODO: potentially the identity. If yes, to remove. */ +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_COMPLEX_PCOH_INTERFACE_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h b/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h new file mode 100644 index 00000000..edf73a18 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_cohomology.h @@ -0,0 +1,159 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Persistence_interface_cohomology.h + * @author Hannah Schreiber + * @brief Contains the @ref Gudhi::multi_persistence::Persistence_interface_cohomology class. + */ + +#ifndef MP_PERSISTENCE_INTERFACE_COHOMOLOGY_H_INCLUDED +#define MP_PERSISTENCE_INTERFACE_COHOMOLOGY_H_INCLUDED + +#include + +#include + +#include +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Persistence_interface_cohomology Persistence_interface_cohomology.h \ + * gudhi/Multi_persistence/Persistence_interface_cohomology.h + * @ingroup multi_persistence + * + * @brief Interface respecting the @ref PersistenceAlgorithm concept to use @ref Slicer with the cohomology + * algorithm implemented in @ref Gudhi::persistent_cohomology::Persistent_cohomology. + * + * @tparam MultiFiltrationValue Filtration value type used in @ref Slicer. + */ +template +class Persistence_interface_cohomology +{ + public: + using PCOH_complex = Multi_parameter_filtered_complex_pcoh_interface; + using Complex = typename PCOH_complex::Complex; /**< Complex type */ + using Dimension = typename PCOH_complex::Dimension; /**< Dimension type */ + using Index = typename PCOH_complex::Simplex_key; /**< Index type */ + using Map = typename PCOH_complex::Map; /**< Map type */ + using Bar = Gudhi::persistence_matrix::Persistence_interval; /**< Bar type */ + using Field_Zp = Gudhi::persistent_cohomology::Field_Zp; + using Persistent_cohomology = Gudhi::persistent_cohomology::Persistent_cohomology; + using Barcode = std::vector; /**< Barcode type */ + template + using As_type = Persistence_interface_cohomology; /**< This type. */ + + static constexpr const auto nullDeath = Bar::inf; + static constexpr const bool is_vine = false; /** False. */ + static constexpr const bool has_rep_cycles = false; /** False. */ + + Persistence_interface_cohomology() : interface_(), barcode_() {} + + // `permutation` is assumed to have stable size, i.e., its address never changes + Persistence_interface_cohomology(const Complex& cpx, const Map& permutation) : interface_(cpx, permutation) + { + _initialize(); + } + + Persistence_interface_cohomology(const Persistence_interface_cohomology& other) = delete; + + // permutation is assumed to be the same than from the copied object, just its address can change + Persistence_interface_cohomology(const Persistence_interface_cohomology& other, const Map& permutation) + : interface_(other.interface_, permutation), barcode_(other.barcode_) + {} + + Persistence_interface_cohomology(Persistence_interface_cohomology&& other) = delete; + + // permutation is assumed to be the same than from the moved object, just its address can change + Persistence_interface_cohomology(Persistence_interface_cohomology&& other, const Map& permutation) + : interface_(std::move(other.interface_), permutation), barcode_(std::move(other.barcode_)) + {} + + ~Persistence_interface_cohomology() = default; + + Persistence_interface_cohomology& operator=(const Persistence_interface_cohomology& other) = delete; + Persistence_interface_cohomology& operator=(Persistence_interface_cohomology&& other) noexcept = delete; + + // TODO: swap? + + template + void reinitialize(const Complex& cpx, const Map& permutation) + { + interface_.reinitialize(cpx, permutation); + _initialize(); + } + + void reset() + { + interface_.reset(); + barcode_.clear(); + } + + [[nodiscard]] bool is_initialized() const { return interface_.is_initialized(); } + + Dimension get_dimension(Index i) const + { + GUDHI_CHECK(is_initialized(), "Dimension can not be computed uninitialized."); + return interface_.dimension(interface_.simplex(i)); + } + + const Barcode& get_barcode() + { + GUDHI_CHECK(is_initialized(), "Barcode can not be computed uninitialized."); + return barcode_; + } + + /** + * @brief Outstream operator. + */ + friend std::ostream& operator<<(std::ostream& stream, const Persistence_interface_cohomology& pers) + { + stream << "Complex:\n"; + stream << pers.interface_ << "\n"; + stream << "Barcode:\n"; + for (const auto bar : pers.barcode_) { + stream << bar << "\n"; + } + stream << "\n"; + + return stream; + } + + private: + PCOH_complex interface_; + Barcode barcode_; + + void _initialize() + { + Persistent_cohomology pcoh(interface_, true); + pcoh.init_coefficients(2); + pcoh.compute_persistent_cohomology_without_optimizations(0); + const auto& pairs = pcoh.get_persistent_pairs(); + + barcode_ = Barcode(pairs.size()); + Index i = 0; + for (const auto& p : pairs) { + auto& b = barcode_[i]; + b.dim = interface_.dimension(get<0>(p)); + b.birth = get<0>(p); + b.death = get<1>(p) == PCOH_complex::null_simplex() ? Bar::inf : get<1>(p); + ++i; + } + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_PERSISTENCE_INTERFACE_COHOMOLOGY_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h b/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h new file mode 100644 index 00000000..3b9d90f6 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_persistence/Persistence_interface_matrix.h @@ -0,0 +1,463 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Persistence_interface_matrix.h + * @author Hannah Schreiber + * @brief Contains the @ref Gudhi::multi_persistence::Persistence_interface_matrix class. + */ + +#ifndef MP_PERSISTENCE_INTERFACE_MATRIX_H_INCLUDED +#define MP_PERSISTENCE_INTERFACE_MATRIX_H_INCLUDED + +#include +#include +#include +#include + +#include + +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Persistence_interface_matrix Persistence_interface_matrix.h \ + * gudhi/Multi_persistence/Persistence_interface_matrix.h + * @ingroup multi_persistence + * + * @brief Interface respecting the @ref PersistenceAlgorithm concept to use @ref Slicer with the homology, vineyard + * and representative cycle algorithms implemented in @ref Gudhi::persistence_matrix::Matrix. + * + * @tparam PosIdxPersistenceMatrixOptions Options respecting the + * @ref Gudhi::persistence_matrix::PersistenceMatrixOptions concept such that, either + * @ref Gudhi::persistence_matrix::PersistenceMatrixOptions::column_indexation_type "column_indexation_type" is + * @ref Gudhi::persistence_matrix::Column_indexation_types::POSITION "POSITION", or, + * @ref Gudhi::persistence_matrix::PersistenceMatrixOptions::is_of_boundary_type "is_of_boundary_type" is true and + * @ref Gudhi::persistence_matrix::PersistenceMatrixOptions::column_indexation_type "column_indexation_type" is + * @ref Gudhi::persistence_matrix::Column_indexation_types::CONTAINER "CONTAINER". + */ +template +class Persistence_interface_matrix +{ + public: + using Options = PosIdxPersistenceMatrixOptions; + using Matrix = Gudhi::persistence_matrix::Matrix; /**< Complex type */ + using Dimension = typename Options::Dimension; /**< Dimension type */ + using Index = typename Options::Index; /**< Index type */ + using Map = std::vector; /**< Map type */ + using Bar = typename Matrix::Bar; /**< Bar type */ + using Cycle = typename Matrix::Cycle; /**< Cycle type */ + template + using As_type = Persistence_interface_matrix; /**< This type. */ + + class Barcode_iterator + : public boost::iterator_facade + { + private: + using Base = boost::iterator_facade; + + public: + using Barcode = typename Matrix::Barcode; + + using difference_type = typename Base::difference_type; + using size_type = std::size_t; + using const_reference = const Bar&; + + Barcode_iterator(const Barcode& barcode, const Map& permutation, size_type pos) + : barcode_(&barcode), + permutation_(&permutation), + currPos_(pos > barcode.size() ? barcode.size() : pos), + currBar_() + { + // end otherwise + if (currPos_ < barcode.size()) _update_bar(); + } + + // necessary for boost::iterator_range to be able to use operator(). + // operator[] not possible in this particular case + Bar operator[](difference_type n) const + { + const auto& b = (*barcode_)[currPos_ + n]; // n is out of range if currPos_ + n is out of range from barcode_ + return Bar(_posToIndex(b.birth), b.death == Bar::inf ? b.death : _posToIndex(b.death), b.dim); + } + + private: + using Pos_index = typename std::tuple_element<2, Bar>::type; + + friend class boost::iterator_core_access; + + bool equal(Barcode_iterator const& other) const + { + return barcode_ == other.barcode_ && permutation_ == other.permutation_ && currPos_ == other.currPos_; + } + + const_reference dereference() const { return currBar_; } + + void increment() + { + ++currPos_; + if (currPos_ < barcode_->size()) _update_bar(); + } + + void decrement() + { + --currPos_; + if (currPos_ < barcode_->size()) _update_bar(); + } + + void advance(difference_type n) + { + currPos_ += n; + if (currPos_ < barcode_->size()) _update_bar(); + } + + difference_type distance_to(const Barcode_iterator& other) const { return other.currPos_ - currPos_; } + + Pos_index _posToIndex(Pos_index pos) const { return (*permutation_)[pos]; } + + void _update_bar() + { + const auto& b = (*barcode_)[currPos_]; + currBar_.dim = b.dim; + currBar_.birth = _posToIndex(b.birth); + currBar_.death = b.death == Bar::inf ? b.death : _posToIndex(b.death); + } + + Barcode const* barcode_; + Map const* permutation_; + size_type currPos_; + Bar currBar_; + }; + + class Cycles_iterator + : public boost::iterator_facade + { + private: + using Base = boost::iterator_facade; + + public: + using Cycles = std::vector; + + using difference_type = typename Base::difference_type; + using size_type = std::size_t; + using const_reference = const Cycle&; + + Cycles_iterator(const Cycles& cycles, const Map& permutation, Map const* idToPos, size_type pos) + : cycles_(&cycles), + permutation_(&permutation), + idToPos_(idToPos), + currPos_(pos > cycles.size() ? cycles.size() : pos), + currCycle_() + { + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + GUDHI_CHECK(idToPos_ != nullptr, "ID to position map has to be set for chain matrices using vineyard."); + } + // end otherwise + if (currPos_ < cycles.size()) _update_cycle(); + } + + // necessary for boost::iterator_range to be able to use operator(). + // operator[] not possible in this particular case + Cycle operator[](difference_type n) const + { + Cycle res((*cycles_)[currPos_ + n]); + for (auto& id : res) { + if constexpr (PosIdxPersistenceMatrixOptions::is_z2) { + id = _id_to_index(id); + } else { + id.first = _id_to_index(id.first); + } + } + return res; + } + + private: + friend class boost::iterator_core_access; + + bool equal(Cycles_iterator const& other) const + { + return cycles_ == other.cycles_ && permutation_ == other.permutation_ && currPos_ == other.currPos_; + } + + const_reference dereference() const { return currCycle_; } + + void increment() + { + ++currPos_; + if (currPos_ < cycles_->size()) _update_cycle(); + } + + void decrement() + { + --currPos_; + if (currPos_ < cycles_->size()) _update_cycle(); + } + + void advance(difference_type n) + { + currPos_ += n; + if (currPos_ < cycles_->size()) _update_cycle(); + } + + difference_type distance_to(const Cycles_iterator& other) const { return other.currPos_ - currPos_; } + + Index _id_to_index(Index id) const + { + if constexpr (Options::is_of_boundary_type || !Options::has_vine_update) { + // works for RU because id == pos, but does not work for chain with vine + // we need a id to pos map in that case + return (*permutation_)[id]; + } else { + return (*permutation_)[(*idToPos_)[id]]; + } + } + + void _update_cycle() + { + const auto& c = (*cycles_)[currPos_]; + currCycle_.resize(c.size()); + for (size_type i = 0; i < c.size(); ++i) { + if constexpr (PosIdxPersistenceMatrixOptions::is_z2) { + currCycle_[i] = _id_to_index(c[i]); + } else { + currCycle_[i].first = _id_to_index(c[i]); + } + } + } + + Cycles const* cycles_; + Map const* permutation_; + Map const* idToPos_; + size_type currPos_; + Cycle currCycle_; + }; + + using Barcode = boost::iterator_range; /**< Barcode type */ + using Cycles = boost::iterator_range; /**< Cycle container type */ + + static constexpr const auto nullDeath = Bar::inf; + /** + * @brief True if and only if PosIdxPersistenceMatrixOptions::has_vine_update is true. + */ + static constexpr const bool is_vine = Options::has_vine_update; + /** + * @brief True if and only if PosIdxPersistenceMatrixOptions::can_retrieve_representative_cycles is true. + */ + static constexpr const bool has_rep_cycles = Options::can_retrieve_representative_cycles; + + Persistence_interface_matrix() : permutation_(nullptr) {} + + // `permutation` is assumed to have stable size, i.e., its address never changes + template + Persistence_interface_matrix(const Complex& cpx, const Map& permutation) + : matrix_(permutation.size()), permutation_(&permutation) + { + static_assert( + Options::column_indexation_type == Gudhi::persistence_matrix::Column_indexation_types::POSITION || + (Options::is_of_boundary_type && + Options::column_indexation_type == Gudhi::persistence_matrix::Column_indexation_types::CONTAINER), + "Matrix has a non supported index scheme."); + + _initialize(cpx); + } + + Persistence_interface_matrix(const Persistence_interface_matrix& other) = delete; + + // permutation is assumed to be the same than from the copied object, just its address can change + Persistence_interface_matrix(const Persistence_interface_matrix& other, const Map& permutation) + : matrix_(other.matrix_), permutation_(other.is_initialized() ? &permutation : nullptr), idToPos_(other.idToPos_) + { + GUDHI_CHECK(!other.is_initialized() || permutation == *other.permutation_, + "Only the address of the permutation vector is allowed to change, not its content."); + } + + Persistence_interface_matrix(Persistence_interface_matrix&& other) = delete; + + // permutation is assumed to be the same than from the moved object, just its address can change + Persistence_interface_matrix(Persistence_interface_matrix&& other, const Map& permutation) + : matrix_(std::move(other.matrix_)), + permutation_(other.is_initialized() ? &permutation : nullptr), + idToPos_(std::move(other.idToPos_)) + { + other.permutation_ = nullptr; + } + + ~Persistence_interface_matrix() = default; + + Persistence_interface_matrix& operator=(const Persistence_interface_matrix& other) = delete; + Persistence_interface_matrix& operator=(Persistence_interface_matrix&& other) noexcept = delete; + + // TODO: swap? + + template + void reinitialize(const Complex& cpx, const Map& permutation) + { + matrix_ = Matrix(permutation.size()); + permutation_ = &permutation; + _initialize(cpx); + } + + void reset() + { + matrix_ = Matrix(); + permutation_ = nullptr; + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + idToPos_->clear(); + } + } + + [[nodiscard]] bool is_initialized() const { return permutation_ != nullptr; } + + Dimension get_dimension(Index i) const + { + GUDHI_CHECK(is_initialized(), "Dimension can not be computed uninitialized."); + return matrix_.get_column_dimension(i); + } + + Barcode get_barcode() + { + GUDHI_CHECK(is_initialized(), "Barcode can not be computed uninitialized."); + + const auto& barcode = matrix_.get_current_barcode(); + return Barcode(Barcode_iterator(barcode, *permutation_, 0), + Barcode_iterator(barcode, *permutation_, barcode.size())); + } + + void vine_swap(Index i) + { + static_assert(is_vine, "`vine_swap` is not enabled with the given options."); + GUDHI_CHECK(is_initialized(), "Vineyard can not be computed uninitialized."); + + if constexpr (!Options::is_of_boundary_type) { + auto id1 = matrix_.get_pivot(i); + auto id2 = matrix_.get_pivot(i + 1); + std::swap((*idToPos_)[id1], (*idToPos_)[id2]); + } + matrix_.vine_swap(i); + } + + Cycles get_representative_cycles(bool update) + { + static_assert(has_rep_cycles, "`get_representative_cycles` is not enabled with the given options."); + GUDHI_CHECK(is_initialized(), "Representative cycles can not be computed uninitialized."); + + if (update) matrix_.update_representative_cycles(); + + const auto& cycles = matrix_.get_representative_cycles(); + Map* idToPosPtr = nullptr; + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + idToPosPtr = &(*idToPos_); + } + return Cycles(Cycles_iterator(cycles, *permutation_, idToPosPtr, 0), + Cycles_iterator(cycles, *permutation_, idToPosPtr, cycles.size())); + } + + Cycle get_representative_cycle(Index barcodeIndex, bool update) + { + static_assert(has_rep_cycles, "`get_representative_cycle` is not enabled with the given options."); + GUDHI_CHECK(is_initialized(), "Representative cycles can not be computed uninitialized."); + + auto id_to_index = [&](Index id) -> Index { + if constexpr (Options::is_of_boundary_type || !Options::has_vine_update) { + // works for RU because id == pos, but does not work for chain with vine + // we need a id to pos map in that case + return (*permutation_)[id]; + } else { + return (*permutation_)[(*idToPos_)[id]]; + } + }; + + if (update) matrix_.update_representative_cycles(); + + const auto& c = matrix_.get_representative_cycle(matrix_.get_current_barcode()[barcodeIndex]); + + Cycle cycle(c.size()); + for (Index i = 0; i < c.size(); ++i) { + if constexpr (PosIdxPersistenceMatrixOptions::is_z2) { + cycle[i] = id_to_index(c[i]); + } else { + cycle[i].first = id_to_index(c[i]); + } + } + return cycle; + } + + /** + * @brief Outstream operator. + */ + friend std::ostream& operator<<(std::ostream& stream, Persistence_interface_matrix& pers) + { + stream << "Matrix:\n"; + stream << "[\n"; + for (auto i = 0U; i < pers.matrix_.get_number_of_columns(); i++) { + stream << "["; + for (const auto& v : pers.matrix_.get_column(i)) stream << v << ", "; + stream << "]\n"; + } + stream << "]\n"; + stream << "Permutation:\n"; + if (pers.permutation_ != nullptr) { + for (auto v : *pers.permutation_) { + stream << v << " "; + } + } + stream << "\n"; + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + stream << "ID to position map:\n"; + for (auto v : *pers.idToPos_) { + stream << v << " "; + } + stream << "\n"; + } + + return stream; + } + + private: + Matrix matrix_; + Map const* permutation_; + std::optional idToPos_; + + template + void _initialize(const Complex& cpx) + { + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + idToPos_.emplace(); + idToPos_->reserve(permutation_->size()); + } + const auto& boundaries = cpx.get_boundaries(); + const auto& dimensions = cpx.get_dimensions(); + // simplex IDs need to be increasing in order, so the original ones cannot be used + Map permutationInv(cpx.get_number_of_cycle_generators()); + Map translated_boundary; + std::size_t id = 0; + for (auto i : *permutation_) { + permutationInv[i] = id; // permutation is assumed to be a valid filtration + translated_boundary.resize(boundaries[i].size()); + for (std::size_t j = 0; j < boundaries[i].size(); ++j) { + translated_boundary[j] = permutationInv[boundaries[i][j]]; + } + std::sort(translated_boundary.begin(), translated_boundary.end()); + matrix_.insert_boundary(id, translated_boundary, dimensions[i]); + if constexpr (Options::has_vine_update && !Options::is_of_boundary_type) { + idToPos_->push_back(id); + } + ++id; // IDs corresponds to the indices in permutation_ + } + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_PERSISTENCE_INTERFACE_MATRIX_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Multi_persistence/Point.h b/multipers/gudhi/gudhi/Multi_persistence/Point.h new file mode 100644 index 00000000..22cccad1 --- /dev/null +++ b/multipers/gudhi/gudhi/Multi_persistence/Point.h @@ -0,0 +1,853 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Point.h + * @author Hannah Schreiber + * @brief Contains the @ref Gudhi::multi_persistence::Point class. + */ + +#ifndef MP_POINT_H_INCLUDED +#define MP_POINT_H_INCLUDED + +#include //std::for_each +#include //std::ostream +#include + +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Point Point.h gudhi/Multi_persistence/Point.h + * @ingroup multi_persistence + * + * @brief Simple point class with possibility to translate and multiply coordinates. + * + * @tparam T Type of the coordinates of the point. + */ +template +class Point +{ + public: + using Container = std::vector; /**< Type of coordinate container. */ + + using value_type = typename Container::value_type; /**< Type of coordinates. */ + using allocator_type = typename Container::allocator_type; /**< Allocator type. */ + using size_type = typename Container::size_type; /**< Size type. */ + using difference_type = typename Container::difference_type; /**< Difference type. */ + using reference = typename Container::reference; /**< Coordinate reference type. */ + using const_reference = typename Container::const_reference; /**< Coordinate const reference type. */ + using pointer = typename Container::pointer; /**< Coordinate pointer type. */ + using iterator = typename Container::iterator; /**< Coordinate iterator type. */ + using const_iterator = typename Container::const_iterator; /**< Coordinate const iterator type. */ + using reverse_iterator = typename Container::reverse_iterator; /**< Coordinate reverse iterator type. */ + using const_reverse_iterator = typename Container::const_reverse_iterator; /**< Coordinate reverse iterator type. */ + + /** + * @brief Default constructor. + */ + Point() = default; + + /** + * @brief Constructs a new point with given number of coordinates. All values are default initialized. + */ + explicit Point(size_type count) : coordinates_(count) {} + + /** + * @brief Constructs a new point with given number of coordinates. All values are initialized with given value. + */ + Point(size_type count, const T &value) : coordinates_(count, value) {} + + /** + * @brief Constructs a new point from the given range. + * + * @tparam InputIt Iterator type that must follow the condition of the corresponding vector constructor. + */ + template + Point(InputIt first, InputIt last) : coordinates_(first, last) + {} + + /** + * @brief Constructs a new point from the given range. + */ + Point(std::initializer_list init) : coordinates_(init.begin(), init.end()) {} + + /** + * @brief Constructs a new point by copying the given container. + */ + Point(const Container &init) : coordinates_(init) {} + + /** + * @brief Constructs a new point by moving the given container. + */ + Point(Container &&init) : coordinates_(std::move(init)) {} + + /** + * @brief Assign copy operator. + */ + Point &operator=(std::initializer_list ilist) + { + coordinates_ = Container(ilist.begin(), ilist.end()); + return *this; + } + + operator std::vector() const { return coordinates_; } + + /** + * @brief At operator. + */ + reference at(size_type pos) { return coordinates_.at(pos); } + + /** + * @brief At operator. + */ + const_reference at(size_type pos) const { return coordinates_.at(pos); } + + /** + * @brief operator[]. + */ + reference operator[](size_type pos) { return coordinates_[pos]; } + + /** + * @brief operator[]. + */ + const_reference operator[](size_type pos) const { return coordinates_[pos]; } + + /** + * @brief Front operator. + */ + reference front() { return coordinates_.front(); } + + /** + * @brief Front operator. + */ + const_reference front() const { return coordinates_.front(); } + + /** + * @brief Back operator. + */ + reference back() { return coordinates_.back(); } + + /** + * @brief Back operator. + */ + const_reference back() const { return coordinates_.back(); } + + /** + * @brief Data operator. + */ + T *data() noexcept { return coordinates_.data(); } + + /** + * @brief Data operator. + */ + const T *data() const noexcept { return coordinates_.data(); } + + /** + * @brief begin. + */ + iterator begin() noexcept { return coordinates_.begin(); } + + /** + * @brief begin. + */ + const_iterator begin() const noexcept { return coordinates_.begin(); } + + /** + * @brief cbegin. + */ + const_iterator cbegin() const noexcept { return coordinates_.cbegin(); } + + /** + * @brief end. + */ + iterator end() noexcept { return coordinates_.end(); } + + /** + * @brief end. + */ + const_iterator end() const noexcept { return coordinates_.end(); } + + /** + * @brief cend. + */ + const_iterator cend() const noexcept { return coordinates_.cend(); } + + /** + * @brief rbegin. + */ + reverse_iterator rbegin() noexcept { return coordinates_.rbegin(); } + + /** + * @brief rbegin. + */ + const_reverse_iterator rbegin() const noexcept { return coordinates_.rbegin(); } + + /** + * @brief crbegin. + */ + const_reverse_iterator crbegin() const noexcept { return coordinates_.crbegin(); } + + /** + * @brief rend. + */ + reverse_iterator rend() noexcept { return coordinates_.rend(); } + + /** + * @brief rend. + */ + const_reverse_iterator rend() const noexcept { return coordinates_.rend(); } + + /** + * @brief crend. + */ + const_reverse_iterator crend() const noexcept { return coordinates_.crend(); } + + /** + * @brief Number of coordinates. + */ + size_type size() const noexcept { return coordinates_.size(); } + + /** + * @brief Swap operator. + */ + void swap(Point &other) noexcept(std::allocator_traits::propagate_on_container_swap::value || + std::allocator_traits::is_always_equal::value) + { + coordinates_.swap(other.coordinates_); + } + + /** + * @brief Swap operator. + */ + friend void swap(Point &p1, Point &p2) noexcept { p1.coordinates_.swap(p2.coordinates_); } + + /** + * @brief Outstream operator. + */ + friend std::ostream &operator<<(std::ostream &os, const Point &point) + { + os << "[ "; + for (const T &p : point) os << p << " "; + os << " ]"; + return os; + } + + /** + * @brief Returns true if and only if all coordinates of the first argument are strictly smaller than the ones of + * the second argument. + * + * Note that this order is not total. + */ + friend bool operator<(const Point &a, const Point &b) + { + if (&a == &b) return false; + GUDHI_CHECK(a.size() == b.size(), "Cannot compare two points with different number of coordinates."); + bool isSame = true; + for (size_type i = 0U; i < a.size(); ++i) { + if (a[i] > b[i] || Gudhi::multi_filtration::_is_nan(a[i]) || Gudhi::multi_filtration::_is_nan(b[i])) return false; + if (isSame && a[i] != b[i]) isSame = false; + } + return !isSame; + } + + /** + * @brief Returns true if and only if all coordinates of the first argument are smaller or equal to the ones of + * the second argument. + * + * Note that this order is not total. + */ + friend bool operator<=(const Point &a, const Point &b) + { + if (&a == &b) return true; + GUDHI_CHECK(a.size() == b.size(), "Cannot compare two points with different number of coordinates."); + for (size_type i = 0U; i < a.size(); ++i) { + if (a[i] > b[i] || Gudhi::multi_filtration::_is_nan(a[i]) || Gudhi::multi_filtration::_is_nan(b[i])) return false; + } + return true; + } + + /** + * @brief Returns true if and only if all coordinates of the first argument are strictly greater than the ones of + * the second argument. + * + * Note that this order is not total. + */ + friend bool operator>(const Point &a, const Point &b) { return b < a; } + + /** + * @brief Returns true if and only if all coordinates of the first argument are greater or equal to the ones of + * the second argument. + * + * Note that this order is not total. + */ + friend bool operator>=(const Point &a, const Point &b) { return b <= a; } + + /** + * @brief Returns `true` if and only if all coordinates of both arguments are equal. + */ + friend bool operator==(const Point &a, const Point &b) + { + if (&a == &b) return true; + if (a.size() != b.size()) return false; + return a.coordinates_ == b.coordinates_; + } + + /** + * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. + */ + friend bool operator!=(const Point &a, const Point &b) { return !(a == b); } + + // ARITHMETIC OPERATORS + + // opposite + /** + * @brief Returns a filtration value such that an entry at index \f$ p \f$ is equal to \f$ -f(p) \f$. + * + * Used conventions: + * - \f$ -NaN = NaN \f$. + * + * @param f Value to opposite. + * @return The opposite of @p f. + */ + friend Point operator-(const Point &f) + { + Point result(f.coordinates_); + std::for_each(result.begin(), result.end(), [](T &v) { v = -v; }); + return result; + } + + // subtraction + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) - val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + template + friend Point operator-(Point f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f -= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Point operator-(Point f, const T &val) + { + f -= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ val - f(p) \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the subtraction. + * @param f Second element of the subtraction. + */ + friend Point operator-(const T &val, Point f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + valF = -valF; + Gudhi::multi_filtration::_add(valF, valR); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) - val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + template + friend Point &operator-=(Point &f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_subtract(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) - val \f$. + * + * Used conventions: + * - \f$ inf - inf = NaN \f$, + * - \f$ -inf - (-inf) = NaN \f$, + * - \f$ NaN - b = NaN \f$, + * - \f$ a - NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the subtraction. + * @param val Second element of the subtraction. + */ + friend Point &operator-=(Point &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_subtract(valF, valR); }); + return f; + } + + // addition + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) + val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + template + friend Point operator+(Point f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f += val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Point operator+(Point f, const T &val) + { + f += val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ val + f(p) \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the addition. + * @param f Second element of the addition. + */ + friend Point operator+(const T &val, Point f) + { + f += val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) + val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + template + friend Point &operator+=(Point &f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_add(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) + val \f$. + * + * Used conventions: + * - \f$ inf + (-inf) = NaN \f$, + * - \f$ -inf + inf = NaN \f$, + * - \f$ NaN + b = NaN \f$, + * - \f$ a + NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the addition. + * @param val Second element of the addition. + */ + friend Point &operator+=(Point &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_add(valF, valR); }); + return f; + } + + // multiplication + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) * val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + template + friend Point operator*(Point f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f *= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Point operator*(Point f, const T &val) + { + f *= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ val * f(p) \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the multiplication. + * @param f Second element of the multiplication. + */ + friend Point operator*(const T &val, Point f) + { + f *= val; + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) * val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + template + friend Point &operator*=(Point &f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_multiply(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) * val \f$. + * + * Used conventions: + * - \f$ inf * 0 = NaN \f$, + * - \f$ 0 * inf = NaN \f$, + * - \f$ -inf * 0 = NaN \f$, + * - \f$ 0 * (-inf) = NaN \f$, + * - \f$ NaN * b = NaN \f$, + * - \f$ a * NaN = NaN \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the multiplication. + * @param val Second element of the multiplication. + */ + friend Point &operator*=(Point &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_multiply(valF, valR); }); + return f; + } + + // division + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) / val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + template + friend Point operator/(Point f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f /= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Point operator/(Point f, const T &val) + { + f /= val; + return f; + } + + /** + * @brief Returns a filtration value such that an entry at index \f$ (p) \f$ is equal to \f$ val / f(p) \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param val First element of the division. + * @param f Second element of the division. + */ + friend Point operator/(const T &val, Point f) + { + f._apply_operation(val, [](T &valF, const T &valR) { + T tmp = valF; + valF = valR; + Gudhi::multi_filtration::_divide(valF, tmp); + }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) / val(p) \f$. + * Both points have to have the same dimension. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + template + friend Point &operator/=(Point &f, const Point &val) + { + GUDHI_CHECK(f.size() == val.size(), "Cannot translate point by a direction with different dimension."); + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_divide(valF, valR); }); + return f; + } + + /** + * @brief Modifies the first parameter such that an entry at index \f$ (p) \f$ is equal to \f$ f(p) / val \f$. + * + * Used conventions: + * - \f$ a / 0 = NaN \f$, + * - \f$ inf / inf = NaN \f$, + * - \f$ -inf / inf = NaN \f$, + * - \f$ inf / -inf = NaN \f$, + * - \f$ -inf / -inf = NaN \f$, + * - \f$ NaN / b = NaN \f$, + * - \f$ a / NaN = NaN \f$, + * - \f$ a / inf = 0 \f$, + * - \f$ a / -inf = 0 \f$. + * + * All NaN values are represented by `std::numeric_limits::quiet_NaN()` independently if + * `std::numeric_limits::has_quiet_NaN` is true or not. + * + * @param f First element of the division. + * @param val Second element of the division. + */ + friend Point &operator/=(Point &f, const T &val) + { + f._apply_operation(val, [](T &valF, const T &valR) { Gudhi::multi_filtration::_divide(valF, valR); }); + return f; + } + + /** + * @brief Plus infinity value of an entry of the filtration value. + */ + constexpr static const T T_inf = Gudhi::multi_filtration::MF_T_inf; + + /** + * @brief Minus infinity value of an entry of the filtration value. + */ + constexpr static const T T_m_inf = Gudhi::multi_filtration::MF_T_m_inf; + + private: + Container coordinates_; /**< Coordinates of the point. */ + + template + void _apply_operation(const Point &range, F &&operate) + { + for (unsigned int p = 0; p < coordinates_.size(); ++p) { + std::forward(operate)(coordinates_[p], range[p]); + } + } + + template + void _apply_operation(const T &val, F &&operate) + { + for (unsigned int i = 0; i < coordinates_.size(); ++i) { + std::forward(operate)(coordinates_[i], val); + } + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_POINT_H_INCLUDED diff --git a/multipers/gudhi/gudhi/One_critical_filtration.h b/multipers/gudhi/gudhi/One_critical_filtration.h deleted file mode 100644 index fe317171..00000000 --- a/multipers/gudhi/gudhi/One_critical_filtration.h +++ /dev/null @@ -1,1441 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2023 Inria - * - * Modification(s): - * - 2024/08 Hannah Schreiber: Generalization to all signed arithmetic types for T + doc - * - YYYY/MM Author: Description of the modification - */ - -/** - * @file One_critical_filtration.h - * @author David Loiseaux - * @brief Contains the @ref Gudhi::multi_filtration::One_critical_filtration class. - */ - -#ifndef ONE_CRITICAL_FILTRATIONS_H_ -#define ONE_CRITICAL_FILTRATIONS_H_ - -#include //std::lower_bound -#include //std::isnan -#include //std::size_t -#include //std::int32_t -#include //std::ostream -#include //std::numerical_limits -#include //std::logic_error -#include - -#include - -namespace Gudhi { -namespace multi_filtration { - -/** - * @class One_critical_filtration one_critical_filtration.h gudhi/one_critical_filtration.h - * @ingroup multi_filtration - * - * @brief Class encoding the apparition time, i.e., filtration value of an object - * (e.g., simplex, cell, abstract algebraic generator) in the setting of 1-critical multiparameter filtrations. - * The class can be used as a vector whose indices correspond to one parameter each. - * It also follows numpy-like broadcast semantic. - * - * @details Inherits of `std::vector`. Overloads `std::numeric_limits` such that: - * - `std::numeric_limits >::has_infinity` returns `true`, - * - `std::numeric_limits >::infinity()` returns @ref One_critical_filtration::inf() "", - * - `std::numeric_limits >::minus_infinity()` returns - * @ref One_critical_filtration::minus_inf() "", - * - `std::numeric_limits >::max()` throws, - * - `std::numeric_limits >::max(n)` returns a @ref One_critical_filtration with `n` - * parameters evaluated at value `std::numeric_limits::max()`, - * - `std::numeric_limits >::quiet_NaN()` returns @ref One_critical_filtration::nan() "". - * - * One critical simplicial filtrations are filtrations such that the lifetime of each object is a positive cone, e.g. - * - \f$ \{ x \in \mathbb R^2 : x>=(1,2)\} \f$ is valid, while - * - \f$ \{ x \in \mathbb R^2 : x>=(1,2)\} \cap \{x \in \mathbb R^2 : x>=(2,1)\} \f$ is not. - * - * If the lifetime corresponds to a union of such positive cones, the filtration is called a multi-critical filtration. - * For those cases, use @ref Multi_critical_filtration instead. - * - * @tparam T Arithmetic type of an entry for one parameter of the filtration value. Has to be **signed** and - * to implement `std::isnan(T)`, `std::numeric_limits::has_quiet_NaN`, `std::numeric_limits::quiet_NaN()`, - * `std::numeric_limits::has_infinity`, `std::numeric_limits::infinity()` and `std::numeric_limits::max()`. - * If `std::numeric_limits::has_infinity` returns `false`, a call to `std::numeric_limits::infinity()` - * can simply throw. Examples are the native types `double`, `float` and `int`. - */ -template -class One_critical_filtration : public std::vector { - private: - using Base = std::vector; - - public: - /** - * @brief Type of the origin of a "lifetime cone", i.e., of a one-critical filtration value. - * Common with @ref Multi_critical_filtration "". In the 1-critical case, simply the class it-self. - */ - using Generator = One_critical_filtration; - - // CONSTRUCTORS - - /** - * @brief Default constructor. Constructs a value at minus infinity. - */ - One_critical_filtration() : Base{-T_inf} {}; - /** - * @brief Constructs a vector of the size of the given number of parameters with -inf as value for each entry. - * - * @warning The vector `{-inf, -inf, ...}` with \f$ n > 1 \f$ entries is not considered as "minus infinity" (the - * method @ref is_minus_inf() will not return true). The `-inf` are just meant as placeholders, at least one entry - * should be modified by the user. Otherwise, either use the static method @ref minus_inf() or set @p n to 1 instead. - * - * @param n Number of parameters. - */ - One_critical_filtration(int n) : Base(n, -T_inf) {}; - /** - * @brief Constructs a vector of the size of the given number of parameters and the given value for each entry. - * - * @warning If @p value is `inf`, `-inf`, or `NaN`, the vector `{value, value, ...}` with \f$ n > 1 \f$ entries - * is not wrong but will not be considered as respectively "infinity", "minus infinity" or "NaN" (the corresponding - * methods @ref is_plus_inf(), @ref is_minus_inf() and @ref is_nan() will return false). For this purpose, please use - * the static methods @ref inf(), @ref minus_inf() and @ref nan() instead. - * - * @param n Number of parameters. - * @param value Value which will be used for each entry. - */ - One_critical_filtration(int n, T value) : Base(n, value) {}; - /** - * @brief Construct a vector from the given initializer list. - * - * @param init Initializer list with values for each parameter. - */ - One_critical_filtration(std::initializer_list init) : Base(init) {}; - /** - * @brief Construct a vector from the given vector. - * - * @param v Vector with values for each parameter. - */ - One_critical_filtration(const std::vector &v) : Base(v) {}; - /** - * @brief Construct a vector from the given vector by moving it to the new vector. - * - * @param v Vector with values for each parameter. - */ - One_critical_filtration(std::vector &&v) : Base(std::move(v)) {}; - /** - * @brief Construct a vector from the range given by the begin and end iterators. - * - * @param it_begin Start of the range. - * @param it_end End of the range. - */ - One_critical_filtration(typename std::vector::iterator it_begin, typename std::vector::iterator it_end) - : Base(it_begin, it_end) {}; - /** - * @brief Construct a vector from the range given by the begin and end const iterators. - * - * @param it_begin Start of the range. - * @param it_end End of the range. - */ - One_critical_filtration(typename std::vector::const_iterator it_begin, - typename std::vector::const_iterator it_end) - : Base(it_begin, it_end) {}; - - // HERITAGE - - using std::vector::operator[]; /**< Inheritance of entry access. */ - using value_type = T; /**< Entry type. */ - - // CONVERTERS - - // like numpy - /** - * @brief Returns a copy with entries casted into the type given as template parameter. - * - * @tparam U New type for the entries. - * @return Copy with new entry type. - */ - template - One_critical_filtration as_type() const { - One_critical_filtration out(0); - out.reserve(Base::size()); - for (std::size_t i = 0u; i < Base::size(); i++) out.push_back(static_cast(Base::operator[](i))); - return out; - } - - // ACCESS - - /** - * @brief Returns the number of parameters of the finite filtration value. If the value is "inf", "-inf" or "NaN", - * returns 1. - * - * @return Number of parameters. - */ - std::size_t num_parameters() const { return Base::size(); } - - /** - * @brief Returns a filtration value for which @ref is_plus_inf() returns `true`. - * - * @return Infinity. - */ - constexpr static One_critical_filtration inf() { return {T_inf}; } - - /** - * @brief Returns a filtration value for which @ref is_minus_inf() returns `true`. - * - * @return Minus infinity. - */ - constexpr static One_critical_filtration minus_inf() { return {-T_inf}; } - - /** - * @brief Returns a filtration value for which @ref is_nan() returns `true`. - * - * @return NaN. - */ - constexpr static One_critical_filtration nan() { - if constexpr (std::numeric_limits::has_quiet_NaN) { - return {std::numeric_limits::quiet_NaN()}; - } else { - return One_critical_filtration(0); // to differentiate it from 0, an empty filtration value can't do much anyway. - } - } - - // DESCRIPTORS - - // TODO: Accept {-inf, -inf, ...} / {inf, inf, ...} / {NaN, NaN, ...} as resp. -inf / inf / NaN. - - /** - * @brief Returns `true` if and only if the filtration value is considered as infinity. - */ - bool is_plus_inf() const { - if (Base::size() != 1) return false; - return (Base::operator[](0) == T_inf); - } - - /** - * @brief Returns `true` if and only if the filtration value is considered as minus infinity. - */ - bool is_minus_inf() const { - if (Base::size() != 1) return false; - return (Base::operator[](0) == -T_inf); - } - - /** - * @brief Returns `true` if and only if the filtration value is considered as NaN. - */ - bool is_nan() const { - if constexpr (std::numeric_limits::has_quiet_NaN) { - if (Base::size() != 1) return false; - return is_nan_(Base::operator[](0)); - } else { - return Base::empty(); - } - } - - /** - * @brief Returns `true` if and only if the filtration value is non-empty and is not considered as infinity, - * minus infinity or NaN. - */ - bool is_finite() const { - if (Base::size() > 1) return true; - if (Base::size() == 0) return false; - auto first_value = Base::operator[](0); // TODO : Maybe check all entries ? - if (is_nan_(first_value) || first_value == -T_inf || first_value == T_inf) return false; - return true; - } - - // COMPARAISON OPERATORS - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is strictly smaller than \f$ b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a < b \f$ and \f$ b < a \f$ returning both false - * does **not** imply \f$ a == b \f$. - */ - friend bool operator<(const One_critical_filtration &a, const One_critical_filtration &b) { - if (a.is_plus_inf() || a.is_nan() || b.is_nan() || b.is_minus_inf()) return false; - if (b.is_plus_inf() || a.is_minus_inf()) return true; - bool isSame = true; - auto n = a.size(); - GUDHI_CHECK(a.size() == b.size(), "Two filtration values with different number of parameters are not comparable."); - for (auto i = 0u; i < n; ++i) { - if (a[i] > b[i]) return false; - if (isSame && a[i] != b[i]) isSame = false; - } - return !isSame; - } - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is smaller or equal than \f$ b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a \le b \f$ and \f$ b \le a \f$ can both return - * `false`. - */ - friend bool operator<=(const One_critical_filtration &a, const One_critical_filtration &b) { - if (a.is_nan() || b.is_nan()) return false; - if (b.is_plus_inf() || a.is_minus_inf()) return true; - if (a.is_plus_inf() || b.is_minus_inf()) return false; - auto n = a.size(); - GUDHI_CHECK(a.size() == b.size(), "Two filtration values with different number of parameters are not comparable."); - for (std::size_t i = 0u; i < n; ++i) { - if (a[i] > b[i]) return false; - } - return true; - } - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is strictly greater than \f$ b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a > b \f$ and \f$ b > a \f$ returning both false - * does **not** imply \f$ a == b \f$. - */ - friend bool operator>(const One_critical_filtration &a, const One_critical_filtration &b) { return b < a; } - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is greater or equal than \f$ b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Note that not all filtration values are comparable. That is, \f$ a \ge b \f$ and \f$ b \ge a \f$ can both return - * `false`. - */ - friend bool operator>=(const One_critical_filtration &a, const One_critical_filtration &b) { return b <= a; } - - /** - * @brief Returns `true` if and only if for each \f$ i \f$, \f$ a[i] \f$ is equal to \f$ b[i] \f$. - */ - friend bool operator==(const One_critical_filtration &a, const One_critical_filtration &b) { - return static_cast(a) == static_cast(b); - } - - /** - * @brief Returns `true` if and only if \f$ a == b \f$ returns `false`. - */ - friend bool operator!=(const One_critical_filtration &a, const One_critical_filtration &b) { return !(a == b); } - - // ARITHMETIC OPERATORS - - // opposite - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ -f[i] \f$. - * - * Used conventions: - * - \f$ -NaN = NaN \f$. - * - * @param f Value to opposite. - * @return The opposite of @p f. - */ - friend One_critical_filtration operator-(const One_critical_filtration &f) { - One_critical_filtration result(0); - result.reserve(f.size()); - for (auto val : f) { - result.push_back(-val); - } - return result; - } - - // subtraction - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ a[i] - b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ inf - inf = NaN \f$, - * - \f$ -inf - (-inf) = NaN \f$, - * - \f$ NaN - b = NaN \f$, - * - \f$ a - NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param a First element of the subtraction. - * @param b Second element of the subtraction. - * @return Entry-wise \f$ a - b \f$. - */ - friend One_critical_filtration operator-(One_critical_filtration a, const One_critical_filtration &b) { - a -= b; - return a; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ f[i] - val \f$. - * - * Used conventions: - * - \f$ inf - inf = NaN \f$, - * - \f$ -inf - (-inf) = NaN \f$, - * - \f$ NaN - b = NaN \f$, - * - \f$ a - NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param f First element of the subtraction. - * @param val Second element of the subtraction. - * @return Entry-wise \f$ f - val \f$. - */ - friend One_critical_filtration operator-(One_critical_filtration f, const T &val) { - f -= val; - return f; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ val - f[i] \f$. - * - * Used conventions: - * - \f$ inf - inf = NaN \f$, - * - \f$ -inf - (-inf) = NaN \f$, - * - \f$ NaN - b = NaN \f$, - * - \f$ a - NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param val First element of the subtraction. - * @param f Second element of the subtraction. - * @return Entry-wise \f$ val - f \f$. - */ - friend One_critical_filtration operator-(const T &val, One_critical_filtration f) { - // TODO: in one go - f = -f; - f += val; - return f; - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] - to\_subtract[i] \f$. - * If @p result and @p to_subtract are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ inf - inf = NaN \f$, - * - \f$ -inf - (-inf) = NaN \f$, - * - \f$ NaN - b = NaN \f$, - * - \f$ a - NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the subtraction. - * @param to_subtract Second element of the subtraction. - * @return Entry-wise \f$ result - to\_subtract \f$. - */ - friend One_critical_filtration &operator-=(One_critical_filtration &result, - const One_critical_filtration &to_subtract) { - if (result.empty()) return result; - - if (result.is_nan() || to_subtract.is_nan() || (result.is_plus_inf() && to_subtract.is_plus_inf()) || - (result.is_minus_inf() && to_subtract.is_minus_inf())) { - result = nan(); - return result; - } - if (result.is_plus_inf() || to_subtract.is_minus_inf()) { - result = inf(); - return result; - } - if (result.is_minus_inf() || to_subtract.is_plus_inf()) { - result = minus_inf(); - return result; - } - - GUDHI_CHECK(result.size() == to_subtract.size(), - "Two filtration values with different number of parameters cannot be subtracted."); - - return apply_operation_with_finite_values_(result, to_subtract, subtract_); - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] - to\_subtract \f$. - * - * Used conventions: - * - \f$ inf - inf = NaN \f$, - * - \f$ -inf - (-inf) = NaN \f$, - * - \f$ NaN - b = NaN \f$, - * - \f$ a - NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the subtraction. - * @param to_subtract Second element of the subtraction. - * @return Entry-wise \f$ result - to\_subtract \f$. - */ - friend One_critical_filtration &operator-=(One_critical_filtration &result, const T &to_subtract) { - if (result.empty()) return result; - - if (result.is_nan() || is_nan_(to_subtract) || (result.is_plus_inf() && to_subtract == T_inf) || - (result.is_minus_inf() && to_subtract == -T_inf)) { - result = nan(); - return result; - } - if (result.is_plus_inf() || to_subtract == -T_inf) { - result = inf(); - return result; - } - if (result.is_minus_inf() || to_subtract == T_inf) { - result = minus_inf(); - return result; - } - - return apply_scalar_operation_on_finite_value_(result, to_subtract, subtract_); - } - - // addition - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ a[i] + b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ NaN + b = NaN \f$, - * - \f$ a + NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param a First element of the addition. - * @param b Second element of the addition. - * @return Entry-wise \f$ a + b \f$. - */ - friend One_critical_filtration operator+(One_critical_filtration a, const One_critical_filtration &b) { - a += b; - return a; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ f[i] + val \f$. - * - * Used conventions: - * - \f$ NaN + b = NaN \f$, - * - \f$ a + NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param f First element of the addition. - * @param val Second element of the addition. - * @return Entry-wise \f$ f + val \f$. - */ - friend One_critical_filtration operator+(One_critical_filtration f, const T &val) { - f += val; - return f; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ val + f[i] \f$. - * - * Used conventions: - * - \f$ NaN + b = NaN \f$, - * - \f$ a + NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param val First element of the addition. - * @param f Second element of the addition. - * @return Entry-wise \f$ val + f \f$. - */ - friend One_critical_filtration operator+(const T &val, One_critical_filtration f) { - f += val; - return f; - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] + to\_add[i] \f$. - * If @p result and @p to_add are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ NaN + b = NaN \f$, - * - \f$ a + NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the addition. - * @param to_add Second element of the addition. - * @return Entry-wise \f$ result + to\_add \f$. - */ - friend One_critical_filtration &operator+=(One_critical_filtration &result, const One_critical_filtration &to_add) { - if (result.empty()) return result; - - if (result.is_nan() || to_add.is_nan() || (result.is_plus_inf() && to_add.is_minus_inf()) || - (result.is_minus_inf() && to_add.is_plus_inf())) { - result = nan(); - return result; - } - if (result.is_plus_inf() || to_add.is_plus_inf()) { - result = inf(); - return result; - } - if (result.is_minus_inf() || to_add.is_minus_inf()) { - result = minus_inf(); - return result; - } - - GUDHI_CHECK(result.size() == to_add.size(), - "Two filtration values with different number of parameters cannot be added."); - - return apply_operation_with_finite_values_(result, to_add, add_); - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] + to\_add \f$. - * - * Used conventions: - * - \f$ NaN + b = NaN \f$, - * - \f$ a + NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the addition. - * @param to_add Second element of the addition. - * @return Entry-wise \f$ result + to\_add \f$. - */ - friend One_critical_filtration &operator+=(One_critical_filtration &result, const T &to_add) { - if (result.empty()) return result; - - if (result.is_nan() || is_nan_(to_add) || (result.is_plus_inf() && to_add == -T_inf) || - (result.is_minus_inf() && to_add == T_inf)) { - result = nan(); - return result; - } - if (result.is_plus_inf() || to_add == T_inf) { - result = inf(); - return result; - } - if (result.is_minus_inf() || to_add == -T_inf) { - result = minus_inf(); - return result; - } - - return apply_scalar_operation_on_finite_value_(result, to_add, add_); - } - - // multiplication - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ a[i] * b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ inf * 0 = NaN \f$, - * - \f$ 0 * inf = NaN \f$, - * - \f$ -inf * 0 = NaN \f$, - * - \f$ 0 * -inf = NaN \f$, - * - \f$ NaN * b = NaN \f$, - * - \f$ a * NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param a First element of the multiplication. - * @param b Second element of the multiplication. - * @return Entry-wise \f$ a * b \f$. - */ - friend One_critical_filtration operator*(One_critical_filtration a, const One_critical_filtration &b) { - a *= b; - return a; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ f[i] * val \f$. - * - * Used conventions: - * - \f$ inf * 0 = NaN \f$, - * - \f$ 0 * inf = NaN \f$, - * - \f$ -inf * 0 = NaN \f$, - * - \f$ 0 * -inf = NaN \f$, - * - \f$ NaN * b = NaN \f$, - * - \f$ a * NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param f First element of the multiplication. - * @param val Second element of the multiplication. - * @return Entry-wise \f$ f * val \f$. - */ - friend One_critical_filtration operator*(One_critical_filtration f, const T &val) { - f *= val; - return f; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ val * f[i] \f$. - * - * Used conventions: - * - \f$ inf * 0 = NaN \f$, - * - \f$ 0 * inf = NaN \f$, - * - \f$ -inf * 0 = NaN \f$, - * - \f$ 0 * -inf = NaN \f$, - * - \f$ NaN * b = NaN \f$, - * - \f$ a * NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param val First element of the multiplication. - * @param f Second element of the multiplication. - * @return Entry-wise \f$ val * f \f$. - */ - friend One_critical_filtration operator*(const T &val, One_critical_filtration f) { - f *= val; - return f; - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] * to\_mul[i] \f$. - * If @p result and @p to_mul are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ inf * 0 = NaN \f$, - * - \f$ 0 * inf = NaN \f$, - * - \f$ -inf * 0 = NaN \f$, - * - \f$ 0 * -inf = NaN \f$, - * - \f$ NaN * b = NaN \f$, - * - \f$ a * NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the multiplication. - * @param to_mul Second element of the multiplication. - * @return Entry-wise \f$ result * to\_mul \f$. - */ - friend One_critical_filtration &operator*=(One_critical_filtration &result, const One_critical_filtration &to_mul) { - if (result.empty()) return result; - - if (result.is_nan() || to_mul.is_nan()) { - result = nan(); - return result; - } - - bool res_is_infinite = result.is_plus_inf() || result.is_minus_inf(); - bool to_mul_is_infinite = to_mul.is_plus_inf() || to_mul.is_minus_inf(); - - if (res_is_infinite && to_mul_is_infinite) { - if (to_mul.is_minus_inf()) { - result[0] = -result[0]; - } - return result; - } - - if (res_is_infinite || to_mul_is_infinite) { - const One_critical_filtration &finite = res_is_infinite ? to_mul : result; - const T infinite = res_is_infinite ? result[0] : to_mul[0]; - result = finite; - return apply_scalar_operation_on_finite_value_(result, infinite, multiply_); - } - - GUDHI_CHECK(result.size() == to_mul.size(), - "Two filtration values with different number of parameters cannot be multiplied."); - - return apply_operation_with_finite_values_(result, to_mul, multiply_); - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] * to\_mul \f$. - * - * Used conventions: - * - \f$ inf * 0 = NaN \f$, - * - \f$ 0 * inf = NaN \f$, - * - \f$ -inf * 0 = NaN \f$, - * - \f$ 0 * -inf = NaN \f$, - * - \f$ NaN * b = NaN \f$, - * - \f$ a * NaN = NaN \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the multiplication. - * @param to_mul Second element of the multiplication. - * @return Entry-wise \f$ result * to\_mul \f$. - */ - friend One_critical_filtration &operator*=(One_critical_filtration &result, const T &to_mul) { - if (result.empty()) return result; - - if (result.is_nan() || is_nan_(to_mul)) { - result = nan(); - return result; - } - - if (result.is_plus_inf() || result.is_minus_inf()) { - if (to_mul == 0) - result = nan(); - else if (to_mul < 0) - result[0] = -result[0]; - return result; - } - - return apply_scalar_operation_on_finite_value_(result, to_mul, multiply_); - } - - // division - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ a[i] / b[i] \f$. - * If @p a and @p b are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ a / 0 = NaN \f$, - * - \f$ inf / inf = NaN \f$, - * - \f$ -inf / inf = NaN \f$, - * - \f$ inf / -inf = NaN \f$, - * - \f$ -inf / -inf = NaN \f$, - * - \f$ NaN / b = NaN \f$, - * - \f$ a / NaN = NaN \f$, - * - \f$ a / inf = 0 \f$, - * - \f$ a / -inf = 0 \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param a First element of the division. - * @param b Second element of the division. - * @return Entry-wise \f$ a / b \f$. - */ - friend One_critical_filtration operator/(One_critical_filtration a, const One_critical_filtration &b) { - a /= b; - return a; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ f[i] / val \f$. - * - * Used conventions: - * - \f$ a / 0 = NaN \f$, - * - \f$ inf / inf = NaN \f$, - * - \f$ -inf / inf = NaN \f$, - * - \f$ inf / -inf = NaN \f$, - * - \f$ -inf / -inf = NaN \f$, - * - \f$ NaN / b = NaN \f$, - * - \f$ a / NaN = NaN \f$, - * - \f$ a / inf = 0 \f$, - * - \f$ a / -inf = 0 \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param f First element of the division. - * @param val Second element of the division. - * @return Entry-wise \f$ f / val \f$. - */ - friend One_critical_filtration operator/(One_critical_filtration f, const T &val) { - f /= val; - return f; - } - - /** - * @brief Returns a filtration value such that an entry at index \f$ i \f$ is equal to \f$ val / f[i] \f$. - * - * Used conventions: - * - \f$ a / 0 = NaN \f$, - * - \f$ inf / inf = NaN \f$, - * - \f$ -inf / inf = NaN \f$, - * - \f$ inf / -inf = NaN \f$, - * - \f$ -inf / -inf = NaN \f$, - * - \f$ NaN / b = NaN \f$, - * - \f$ a / NaN = NaN \f$, - * - \f$ a / inf = 0 \f$, - * - \f$ a / -inf = 0 \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param val First element of the division. - * @param f Second element of the division. - * @return Entry-wise \f$ val / f \f$. - */ - friend One_critical_filtration operator/(const T &val, const One_critical_filtration &f) { - if (f.empty()) return f; - if (is_nan_(val) || f.is_nan()) return nan(); - - One_critical_filtration result(f.size(), val); - result /= f; - return result; - } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] / to\_div[i] \f$. - * If @p result and @p to_div are both not infinite or NaN, they have to have the same number of parameters. - * - * Used conventions: - * - \f$ a / 0 = NaN \f$, - * - \f$ inf / inf = NaN \f$, - * - \f$ -inf / inf = NaN \f$, - * - \f$ inf / -inf = NaN \f$, - * - \f$ -inf / -inf = NaN \f$, - * - \f$ NaN / b = NaN \f$, - * - \f$ a / NaN = NaN \f$, - * - \f$ a / inf = 0 \f$, - * - \f$ a / -inf = 0 \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the division. - * @param to_div Second element of the division. - * @return Entry-wise \f$ result / to\_div \f$. - */ - friend One_critical_filtration &operator/=(One_critical_filtration &result, const One_critical_filtration &to_div) { - if (result.empty()) return result; - - bool res_is_infinite = result.is_plus_inf() || result.is_minus_inf(); - bool to_div_is_infinite = to_div.is_plus_inf() || to_div.is_minus_inf(); - - if (result.is_nan() || to_div.is_nan() || (res_is_infinite && to_div_is_infinite)) { - result = nan(); - return result; - } - - if (to_div_is_infinite) { - return apply_scalar_operation_on_finite_value_with_all_nan_possible_(result, to_div[0], divide_); - } - - GUDHI_CHECK(res_is_infinite || result.size() == to_div.size(), - "Two filtration values with different number of parameters cannot be divided."); - - if (res_is_infinite) { - result.resize(to_div.size(), result[0]); - } - - return apply_operation_with_finite_values_(result, to_div, divide_); - } - - constexpr static bool is_multicritical() { return false; } - - /** - * @brief Modifies the first parameters such that an entry at index \f$ i \f$ is equal to - * \f$ result[i] / to\_div \f$. - * - * Used conventions: - * - \f$ a / 0 = NaN \f$, - * - \f$ inf / inf = NaN \f$, - * - \f$ -inf / inf = NaN \f$, - * - \f$ inf / -inf = NaN \f$, - * - \f$ -inf / -inf = NaN \f$, - * - \f$ NaN / b = NaN \f$, - * - \f$ a / NaN = NaN \f$, - * - \f$ a / inf = 0 \f$, - * - \f$ a / -inf = 0 \f$. - * - * If `std::numeric_limits::has_quiet_NaN` is false, then the returned filtration value will be @ref nan() - * if any operation results in NaN, not only if all operations result in NaN. - * - * @param result First element of the division. - * @param to_div Second element of the division. - * @return Entry-wise \f$ result / to\_div \f$. - */ - friend One_critical_filtration &operator/=(One_critical_filtration &result, const T &to_div) { - if (result.empty()) return result; - - bool res_is_infinite = result.is_plus_inf() || result.is_minus_inf(); - bool to_div_is_infinite = to_div == T_inf || to_div == -T_inf; - - if (to_div == 0 || is_nan_(to_div) || result.is_nan() || (res_is_infinite && to_div_is_infinite)) { - result = nan(); - return result; - } - - if (res_is_infinite) { - if (to_div < 0) result[0] = -result[0]; - return result; - } - - return apply_scalar_operation_on_finite_value_with_all_nan_possible_(result, to_div, divide_); - } - - // MODIFIERS - - /** - * @brief Sets the filtration value to the least common upper bound between the current value and the given value. - * - * More formally, it pushes the current filtration value to the cone \f$ \{ y \in \mathbb R^n : y \ge x \} \f$ - * originating in the given filtration value \f$ x \f$. The resulting value corresponds to the intersection of both - * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \ge this \} \cap \{ y \in \mathbb R^n : y \ge x \} \f$. - * - * @param x The target filtration value towards which to push. - * @return True if and only if the value of this actually changed. - */ - bool push_to_least_common_upper_bound(const One_critical_filtration &x) { - if (this->is_plus_inf() || this->is_nan() || x.is_nan() || x.is_minus_inf()) return false; - if (x.is_plus_inf() || this->is_minus_inf()) { - if (!x.is_minus_inf() && !this->is_plus_inf()) { - *this = x; - return true; - } - return false; - } - - GUDHI_CHECK(this->num_parameters() == x.num_parameters(), - "A filtration value cannot be pushed to another one with different numbers of parameters."); - - bool modified = false; - for (std::size_t i = 0; i < x.num_parameters(); i++) { - modified |= Base::operator[](i) < x[i]; - Base::operator[](i) = Base::operator[](i) > x[i] ? Base::operator[](i) : x[i]; - } - return modified; - } - - /** - * @brief Sets the filtration value to the greatest common lower bound between the current value and the given value. - * - * More formally, it pulls the current filtration value to the cone \f$ \{ y \in \mathbb R^n : y \le x \} \f$ - * originating in the given filtration value \f$ x \f$. The resulting value corresponds to the intersection of both - * cones: \f$ \mathrm{this} = \min \{ y \in \mathbb R^n : y \le this \} \cap \{ y \in \mathbb R^n : y \le x \} \f$. - * - * @param x The target filtration value towards which to pull. - * @return True if and only if the value of this actually changed. - */ - bool pull_to_greatest_common_lower_bound(const One_critical_filtration &x) { - if (x.is_plus_inf() || this->is_nan() || x.is_nan() || this->is_minus_inf()) return false; - if (this->is_plus_inf() || x.is_minus_inf()) { - if (!x.is_plus_inf() && !this->is_minus_inf()) { - *this = x; - return true; - } - return false; - } - - GUDHI_CHECK(this->num_parameters() == x.num_parameters(), - "A filtration value cannot be pulled to another one with different numbers of parameters."); - - bool modified = false; - for (std::size_t i = 0; i < x.num_parameters(); i++) { - modified |= Base::operator[](i) > x[i]; - Base::operator[](i) = Base::operator[](i) > x[i] ? x[i] : Base::operator[](i); - } - return modified; - } - - /** - * @brief Projects the filtration value into the given grid. If @p coordinate is false, the entries are set to - * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices - * of those nearest upper bound values. - * The grid has to be represented as a vector of ordered ranges of values convertible into `T`. An index - * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in the filtration value. - * The ranges correspond to the possible values of the parameters, ordered by increasing value, forming therefore - * all together a 2D grid. - * - * @tparam oned_array A range of values convertible into `T` ordered by increasing value. Has to implement - * a begin, end and operator[] method. - * @param grid Vector of @p oned_array with size at least number of filtration parameters. - * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, - * the values are set to the values at the coordinates of the projection. - */ - template - void project_onto_grid(const std::vector &grid, bool coordinate = true) { - GUDHI_CHECK(grid.size() >= Base::size(), - "The grid should not be smaller than the number of parameters in the filtration value."); - for (std::size_t parameter = 0u; parameter < Base::size(); ++parameter) { - const auto &filtration = grid[parameter]; - int num_filtration_values = filtration.size(); - auto this_at_param = static_cast(Base::operator[](parameter)); - auto it = std::lower_bound(filtration.begin(), filtration.end(), this_at_param); - std::size_t idx; - if (it == filtration.end()) { - idx = num_filtration_values - 1; - } else if (it == filtration.begin()) { - idx = 0; - } else { - auto prev = it - 1; - idx = (std::abs(*prev - this_at_param) <= std::abs(*it - this_at_param)) - ? std::distance(filtration.begin(), prev) - : std::distance(filtration.begin(), it); - } - Base::operator[](parameter) = coordinate ? static_cast(idx) : static_cast(filtration[idx]); - } - } - - // FONCTIONNALITIES - - /** - * @brief Computes the scalar product of the given filtration value with the given vector. - * - * @tparam U Arithmetic type of the result. Default value: `T`. - * @param f Filtration value. - * @param x Vector of coefficients. - * @return Scalar product of @p f with @p x. - */ - template - friend U compute_linear_projection(const One_critical_filtration &f, const std::vector &x) { - U projection = 0; - std::size_t size = std::min(x.size(), f.size()); - for (std::size_t i = 0u; i < size; i++) projection += x[i] * static_cast(f[i]); - return projection; - } - - /** - * @brief Computes the norm of the given filtration value. - * - * @param f Filtration value. - * @return The norm of @p f. - */ - friend T compute_norm(const One_critical_filtration &f) { - T out = 0; - for (auto &val : f) out += (val * val); - if constexpr (std::is_integral_v) { - // to avoid Windows issue which don't know how to cast integers for cmath methods - return std::sqrt(static_cast(out)); - } else { - return std::sqrt(out); - } - } - - /** - * @brief Computes the euclidean distance from the first parameter to the second parameter. - * - * @param f Start filtration value. - * @param other End filtration value. - * @return Euclidean distance between @p f and @p other. - */ - friend T compute_euclidean_distance_to(const One_critical_filtration &f, const One_critical_filtration &other) { - T out = 0; - for (std::size_t i = 0u; i < other.size(); i++) { - out += (f[i] - other[i]) * (f[i] - other[i]); - } - if constexpr (std::is_integral_v) { - // to avoid Windows issue which don't know how to cast integers for cmath methods - return std::sqrt(static_cast(out)); - } else { - return std::sqrt(out); - } - } - - /** - * @brief Computes the coordinates in the given grid, corresponding to the nearest upper bounds of the entries - * in the given filtration value. - * The grid has to be represented as a vector of vectors of ordered values convertible into `out_type`. An index - * \f$ i \f$ of the vector corresponds to the same parameter as the index \f$ i \f$ in the filtration value. - * The inner vectors correspond to the possible values of the parameters, ordered by increasing value, - * forming therefore all together a 2D grid. - * - * @tparam out_type Signed arithmetic type. Default value: std::int32_t. - * @tparam U Type which is convertible into `out_type`. - * @param f Filtration value to project. - * @param grid Vector of vectors to project into. - * @return Filtration value \f$ out \f$ whose entry correspond to the indices of the projected values. That is, - * the projection of \f$ f[i] \f$ is \f$ grid[i][out[i]] \f$. - */ - template - friend One_critical_filtration compute_coordinates_in_grid(One_critical_filtration f, - const std::vector > &grid) { - // TODO: by replicating the code of "project_onto_grid", this could be done with just one copy - // instead of two. But it is not clear if it is really worth it, i.e., how much the change in type is really - // necessary in the use cases. To see later. - f.project_onto_grid(grid); - if constexpr (std::is_same_v) { - return f; - } else { - return f.as_type(); - } - } - - /** - * @brief Computes the values in the given grid corresponding to the coordinates given by the given filtration - * value. That is, if \f$ out \f$ is the result, \f$ out[i] = grid[i][f[i]] \f$. Assumes therefore, that the - * values stored in the filtration value corresponds to indices existing in the given grid. - * - * @tparam U Signed arithmetic type. - * @param f Filtration value storing coordinates compatible with `grid`. - * @param grid Vector of vector. - * @return Filtration value \f$ out \f$ whose entry correspond to \f$ out[i] = grid[i][f[i]] \f$. - */ - template - friend One_critical_filtration evaluate_coordinates_in_grid(const One_critical_filtration &f, - const std::vector > &grid) { - One_critical_filtration pushed_value(f.size()); - - GUDHI_CHECK(grid.size() == f.size(), - "The size of the grid should correspond to the number of parameters in the filtration value."); - - U grid_inf = One_critical_filtration::T_inf; - - for (std::size_t parameter = 0u; parameter < grid.size(); ++parameter) { - const auto &filtration = grid[parameter]; - const auto &c = f[parameter]; - pushed_value[parameter] = c == f.T_inf ? grid_inf : filtration[c]; - } - return pushed_value; - } - - // UTILITIES - - /** - * @brief Outstream operator. - */ - friend std::ostream &operator<<(std::ostream &stream, const One_critical_filtration &f) { - if (f.is_plus_inf()) { - stream << "[inf, ..., inf]"; - return stream; - } - if (f.is_minus_inf()) { - stream << "[-inf, ..., -inf]"; - return stream; - } - if (f.is_nan()) { - stream << "[NaN]"; - return stream; - } - if (f.empty()) { - stream << "[]"; - return stream; - } - stream << "["; - for (std::size_t i = 0; i < f.size() - 1; i++) { - stream << f[i] << ", "; - } - stream << f.back(); - stream << "]"; - return stream; - } - - friend bool unify_lifetimes(One_critical_filtration &f1, const One_critical_filtration &f2) { - // WARNING: costly check - GUDHI_CHECK(f1 <= f2 || f2 <= f1, "When 1-critical, two non-comparable filtration values cannot be unified."); - - return f1.pull_to_greatest_common_lower_bound(f2); - } - - friend bool intersect_lifetimes(One_critical_filtration &f1, const One_critical_filtration &f2) { - return f1.push_to_least_common_upper_bound(f2); - } - - friend char *serialize_trivial(const One_critical_filtration &value, char *start) { - const auto length = value.size(); - const std::size_t arg_size = sizeof(T) * length; - const std::size_t type_size = sizeof(typename One_critical_filtration::Base::size_type); - memcpy(start, &length, type_size); - memcpy(start + type_size, value.data(), arg_size); - return start + arg_size + type_size; - } - - friend const char *deserialize_trivial(One_critical_filtration &value, const char *start) { - const std::size_t type_size = sizeof(typename One_critical_filtration::Base::size_type); - typename One_critical_filtration::Base::size_type length; - memcpy(&length, start, type_size); - std::size_t arg_size = sizeof(T) * length; - value.resize(length); - memcpy(value.data(), start + type_size, arg_size); - return start + arg_size + type_size; - } - - friend std::size_t get_serialization_size_of(const One_critical_filtration &value) { - return sizeof(typename One_critical_filtration::Base::size_type) + sizeof(T) * value.size(); - } - - /** - * @brief Infinity value of an entry of the filtration value. - */ - constexpr static const T T_inf = - std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); - - /** - * @brief Indicates if the class manages multi-critical filtration values. - */ - constexpr static bool is_multi_critical = false; - - private: - static bool is_nan_(T val) { - if constexpr (std::is_integral_v) { - // to avoid Windows issue which don't know how to cast integers for cmath methods - return false; - } else { - return std::isnan(val); - } - } - - constexpr static bool subtract_(T &v1, T v2) { return add_(v1, -v2); } - - constexpr static bool add_(T &v1, T v2) { - if (is_nan_(v1) || is_nan_(v2) || (v1 == T_inf && v2 == -T_inf) || (v1 == -T_inf && v2 == T_inf)) { - v1 = std::numeric_limits::quiet_NaN(); - return false; - } - if (v1 == T_inf || v1 == -T_inf) { - return true; - } - if (v2 == T_inf || v2 == -T_inf) { - v1 = v2; - return true; - } - - v1 += v2; - return true; - } - - constexpr static bool multiply_(T &v1, T v2) { - bool v1_is_infinite = v1 == T_inf || v1 == -T_inf; - bool v2_is_infinite = v2 == T_inf || v2 == -T_inf; - - if (is_nan_(v1) || is_nan_(v2) || (v1_is_infinite && v2 == 0) || (v1 == 0 && v2_is_infinite)) { - v1 = std::numeric_limits::quiet_NaN(); - return false; - } - - if ((v1 == T_inf && v2 > 0) || (v1 == -T_inf && v2 < 0) || (v1 < 0 && v2 == -T_inf) || (v1 > 0 && v2 == T_inf)) { - v1 = T_inf; - return true; - } - - if ((v1 == T_inf && v2 < 0) || (v1 == -T_inf && v2 > 0) || (v1 > 0 && v2 == -T_inf) || (v1 < 0 && v2 == T_inf)) { - v1 = -T_inf; - return true; - } - - v1 *= v2; - return true; - } - - constexpr static bool divide_(T &v1, T v2) { - bool v1_is_infinite = v1 == T_inf || v1 == -T_inf; - bool v2_is_infinite = v2 == T_inf || v2 == -T_inf; - - if (is_nan_(v1) || is_nan_(v2) || v2 == 0 || (v1_is_infinite && v2_is_infinite)) { - v1 = std::numeric_limits::quiet_NaN(); - return false; - } - - if (v1 == 0 || (v1_is_infinite && v2 > 0)) return true; - - if (v1_is_infinite && v2 < 0) { - v1 = -v1; - return true; - } - - if (v2_is_infinite) { - v1 = 0; - return true; - } - - v1 /= v2; - return true; - } - - constexpr static bool update_sign_(T toComp, int &sign) { - if (toComp == T_inf) { - if (sign == 0) - sign = 1; - else if (sign == -1) - return false; - } else if (toComp == -T_inf) { - if (sign == 0) - sign = -1; - else if (sign == 1) - return false; - } else { - return false; - } - - return true; - } - - template - static One_critical_filtration &apply_operation_with_finite_values_(One_critical_filtration &result, - const One_critical_filtration &to_operate, - F &&operate) { - bool allSameInf = true; - bool allNaN = true; - int sign = 0; - for (auto i = 0u; i < result.size(); ++i) { - if (operate(result[i], to_operate[i])) { - allNaN = false; - } else { - if constexpr (!std::numeric_limits::has_quiet_NaN) { - result = nan(); - return result; - } - } - if (allSameInf) allSameInf = update_sign_(result[i], sign); - } - - if (allSameInf) result = (sign == 1 ? inf() : minus_inf()); - if (allNaN) result = nan(); - - return result; - } - - template - static One_critical_filtration &apply_scalar_operation_on_finite_value_(One_critical_filtration &result, - const T &to_operate, - F &&operate) { - for (auto &val : result) { - if constexpr (std::numeric_limits::has_quiet_NaN) { - operate(val, to_operate); - } else { - if (!operate(val, to_operate)) { - result = nan(); - return result; - } - } - } - - return result; - } - - template - static One_critical_filtration &apply_scalar_operation_on_finite_value_with_all_nan_possible_( - One_critical_filtration &result, - const T &to_operate, - F &&operate) { - bool allNaN = true; - - for (auto &val : result) { - if (operate(val, to_operate)) { - allNaN = false; - } else { - if constexpr (!std::numeric_limits::has_quiet_NaN) { - result = nan(); - return result; - } - } - } - if (allNaN) result = nan(); - - return result; - } -}; - -} // namespace multi_filtration -} // namespace Gudhi - -namespace std { - -template -class numeric_limits > { - public: - static constexpr bool has_infinity = true; - - static constexpr Gudhi::multi_filtration::One_critical_filtration infinity() noexcept { - return Gudhi::multi_filtration::One_critical_filtration::inf(); - }; - - // non-standard - static constexpr Gudhi::multi_filtration::One_critical_filtration minus_infinity() noexcept { - return Gudhi::multi_filtration::One_critical_filtration::minus_inf(); - }; - - static constexpr Gudhi::multi_filtration::One_critical_filtration max() noexcept(false) { - throw std::logic_error( - "The maximal value cannot be represented with no finite numbers of parameters." - "Use `max(number_of_parameters)` instead"); - }; - - // non-standard, so I don't want to define a default value. - static constexpr Gudhi::multi_filtration::One_critical_filtration max(unsigned int n) noexcept { - return Gudhi::multi_filtration::One_critical_filtration(n, std::numeric_limits::max()); - }; - - static constexpr Gudhi::multi_filtration::One_critical_filtration quiet_NaN() noexcept { - return Gudhi::multi_filtration::One_critical_filtration::nan(); - }; -}; - -} // namespace std - -#endif // ONE_CRITICAL_FILTRATIONS_H_ diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h b/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h index ba8f181b..814af580 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix.h @@ -30,82 +30,86 @@ namespace persistence_matrix { * * @brief A @ref basematrix "basic matrix" structure allowing to easily manipulate and access entire columns and rows, * but not individual entries. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template class Base_matrix : public Master_matrix::template Base_swap_option >, - protected Master_matrix::Matrix_row_access_option + protected Master_matrix::Matrix_row_access_option { + private: + using Swap_opt = typename Master_matrix::template Base_swap_option >; + using RA_opt = typename Master_matrix::Matrix_row_access_option; + public: - using Index = typename Master_matrix::Index; /**< Container index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Index = typename Master_matrix::Index; /**< Container index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of a field element. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of the column container. */ - using Row = typename Master_matrix::Row; /**< Row type, - only necessary with row access option. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to - necessary external classes. */ + using Field_element = typename Master_matrix::Element; /**< Type of a field element. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of the column container. */ + using Row = typename Master_matrix::Row; /**< Row type, + only necessary with row access option. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ /** * @brief Constructs an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Base_matrix(Column_settings* colSettings); /** * @brief Constructs a matrix from the given ordered columns. The columns are inserted in the given order. - * + * * @tparam Container Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. * @param columns A vector of @ref Matrix::Entry_representative ranges to construct the columns from. * The content of the ranges are assumed to be sorted by increasing ID value. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Base_matrix(const std::vector& columns, - Column_settings* colSettings); + Base_matrix(const std::vector& columns, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Base_matrix(unsigned int numberOfColumns, Column_settings* colSettings); /** * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ - Base_matrix(const Base_matrix& matrixToCopy, - Column_settings* colSettings = nullptr); + Base_matrix(const Base_matrix& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Base_matrix(Base_matrix&& other) noexcept; + ~Base_matrix() = default; + /** * @brief Inserts a new ordered column at the end of the matrix by copying the given range of * @ref Matrix::Entry_representative. The content of the range is assumed to be sorted by increasing ID value. - * + * * @tparam Container Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() method. * @param column Range of @ref Matrix::Entry_representative from which the column has to be constructed. Assumed to be - * ordered by increasing ID value. + * ordered by increasing ID value. */ template void insert_column(const Container& column); @@ -113,35 +117,36 @@ class Base_matrix : public Master_matrix::template Base_swap_option void insert_column(const Container& column, Index columnIndex); /** * @brief Same as @ref insert_column, only for interface purposes. The given dimension is ignored and not stored. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Range of @ref Matrix::Entry_representative from which the column has to be constructed. Assumed to - * be ordered by increasing ID value. + * be ordered by increasing ID value. * @param dim Ignored. */ template - void insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. * * Note that before returning the column, all column entries can eventually be reordered, if lazy swaps occurred. * It is therefore recommended to avoid calling @ref get_column between column or row swaps, otherwise the benefits * of the the laziness is lost. - * + * * @param columnIndex @ref MatIdx index of the column to return. * @return Reference to the column. */ @@ -149,12 +154,12 @@ class Base_matrix : public Master_matrix::template Base_swap_option - void multiply_target_and_add_to(const Entry_range_or_column_index& sourceColumn, + void multiply_target_and_add_to(const Entry_range_or_column_index& sourceColumn, const Field_element& coefficient, Index targetColumnIndex); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. - * + * * @tparam Entry_range_or_column_index Either a range of @ref Entry with a begin() and end() method, * or any integer type. * @param coefficient Value to multiply. @@ -249,26 +254,26 @@ class Base_matrix : public Master_matrix::template Base_swap_option - void multiply_source_and_add_to(const Field_element& coefficient, + void multiply_source_and_add_to(const Field_element& coefficient, const Entry_range_or_column_index& sourceColumn, Index targetColumnIndex); /** * @brief Zeroes the entry at the given coordinates. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. */ void zero_entry(Index columnIndex, Index rowIndex); /** * @brief Zeroes the column at the given index. - * + * * @param columnIndex @ref MatIdx index of the column to zero. */ void zero_column(Index columnIndex); /** * @brief Indicates if the entry at given coordinates has value zero. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -277,7 +282,7 @@ class Base_matrix : public Master_matrix::template Base_swap_option >&>(matrix1), - static_cast >&>(matrix2)); + friend void swap(Base_matrix& matrix1, Base_matrix& matrix2) noexcept + { + swap(static_cast(matrix1), static_cast(matrix2)); matrix1.matrix_.swap(matrix2.matrix_); std::swap(matrix1.nextInsertIndex_, matrix2.nextInsertIndex_); std::swap(matrix1.colSettings_, matrix2.colSettings_); if constexpr (Master_matrix::Option_list::has_row_access) { - swap(static_cast(matrix1), - static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); } } - void print(Index startCol = 0, Index endCol = -1, Index startRow = 0, Index endRow = -1); // for debug + void print(); // for debug private: - using Swap_opt = typename Master_matrix::template Base_swap_option >; - using RA_opt = typename Master_matrix::Matrix_row_access_option; using Column_container = typename Master_matrix::Column_container; using Entry_representative = - typename std::conditional - >::type; + typename std::conditional >::type; friend Swap_opt; // direct access to matrix_ to avoid row reorder. - Column_container matrix_; /**< Column container. */ - Index nextInsertIndex_; /**< Next unused column index. */ - Column_settings* colSettings_; /**< Entry factory. */ + Column_container matrix_; /**< Column container. */ + Index nextInsertIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Entry factory. */ template void _insert(const Container& column, Index columnIndex, Dimension dim); @@ -352,8 +359,7 @@ inline Base_matrix::Base_matrix(Column_settings* colSettings) template template -inline Base_matrix::Base_matrix(const std::vector& columns, - Column_settings* colSettings) +inline Base_matrix::Base_matrix(const std::vector& columns, Column_settings* colSettings) : Swap_opt(columns.size()), // not ideal if max row index is much smaller than max column index, does that happen often? RA_opt(columns.size()), @@ -372,8 +378,7 @@ inline Base_matrix::Base_matrix(const std::vector& col } template -inline Base_matrix::Base_matrix(unsigned int numberOfColumns, - Column_settings* colSettings) +inline Base_matrix::Base_matrix(unsigned int numberOfColumns, Column_settings* colSettings) : Swap_opt(numberOfColumns), RA_opt(numberOfColumns), matrix_(!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access @@ -387,16 +392,15 @@ inline Base_matrix::Base_matrix(unsigned int numberOfColumns, } template -inline Base_matrix::Base_matrix(const Base_matrix& matrixToCopy, - Column_settings* colSettings) +inline Base_matrix::Base_matrix(const Base_matrix& matrixToCopy, Column_settings* colSettings) : Swap_opt(static_cast(matrixToCopy)), RA_opt(static_cast(matrixToCopy)), nextInsertIndex_(matrixToCopy.nextInsertIndex_), colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) { matrix_.reserve(matrixToCopy.matrix_.size()); - for (const auto& cont : matrixToCopy.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : matrixToCopy.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -410,48 +414,49 @@ inline Base_matrix::Base_matrix(Base_matrix&& other) noexcept RA_opt(std::move(static_cast(other))), matrix_(std::move(other.matrix_)), nextInsertIndex_(std::exchange(other.nextInsertIndex_, 0)), - colSettings_(std::exchange(other.colSettings_, nullptr)) -{} + colSettings_(std::exchange(other.colSettings_, nullptr)) +{ +} template template -inline void Base_matrix::insert_column(const Container& column) +inline void Base_matrix::insert_column(const Container& column) { - //TODO: dim not actually stored right now, so either get rid of it or store it again + // TODO: dim not actually stored right now, so either get rid of it or store it again _insert(column, nextInsertIndex_, column.size() == 0 ? 0 : column.size() - 1); ++nextInsertIndex_; } template template -inline void Base_matrix::insert_column(const Container& column, Index columnIndex) +inline void Base_matrix::insert_column(const Container& column, Index columnIndex) { static_assert(!Master_matrix::Option_list::has_row_access, "Columns have to be inserted at the end of the matrix when row access is enabled."); if (columnIndex >= nextInsertIndex_) nextInsertIndex_ = columnIndex + 1; - //TODO: dim not actually stored right now, so either get rid of it or store it again + // TODO: dim not actually stored right now, so either get rid of it or store it again _insert(column, columnIndex, column.size() == 0 ? 0 : column.size() - 1); } template template -inline void Base_matrix::insert_boundary(const Boundary_range& boundary, Dimension dim) +inline void Base_matrix::insert_boundary(const Boundary_range& boundary, Dimension dim) { - if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; - //TODO: dim not actually stored right now, so either get rid of it or store it again + if (dim == Master_matrix::template get_null_value()) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + // TODO: dim not actually stored right now, so either get rid of it or store it again _insert(boundary, nextInsertIndex_++, dim); } template -inline typename Base_matrix::Column& Base_matrix::get_column(Index columnIndex) +inline typename Base_matrix::Column& Base_matrix::get_column(Index columnIndex) { _orderRowsIfNecessary(); return _get_column(columnIndex); } template -inline typename Base_matrix::Row& Base_matrix::get_row(Index rowIndex) +inline typename Base_matrix::Row& Base_matrix::get_row(Index rowIndex) { static_assert(Master_matrix::Option_list::has_row_access, "Row access has to be enabled for this method."); @@ -460,7 +465,7 @@ inline typename Base_matrix::Row& Base_matrix::get } template -inline void Base_matrix::remove_column(Index columnIndex) +inline void Base_matrix::remove_column(Index columnIndex) { static_assert(Master_matrix::Option_list::has_map_column_container, "'remove_column' is not implemented for the chosen options."); @@ -472,9 +477,9 @@ inline void Base_matrix::remove_column(Index columnIndex) } template -inline void Base_matrix::remove_last() +inline void Base_matrix::remove_last() { - if (nextInsertIndex_ == 0) return; //empty matrix + if (nextInsertIndex_ == 0) return; // empty matrix --nextInsertIndex_; // assumes that eventual "holes" left at unused indices are considered as empty columns. if constexpr (Master_matrix::Option_list::has_map_column_container) { @@ -491,21 +496,19 @@ inline void Base_matrix::remove_last() } template -inline void Base_matrix::erase_empty_row(Index rowIndex) +inline void Base_matrix::erase_empty_row(Index rowIndex) { if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { RA_opt::erase_empty_row(_get_real_row_index(rowIndex)); } if constexpr ((Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) && Master_matrix::Option_list::has_map_column_container) { - auto it = Swap_opt::indexToRow_.find(rowIndex); - Swap_opt::rowToIndex_.erase(it->second); - Swap_opt::indexToRow_.erase(it); + Swap_opt::_erase_row(rowIndex); } } template -inline typename Base_matrix::Index Base_matrix::get_number_of_columns() const +inline typename Base_matrix::Index Base_matrix::get_number_of_columns() const { if constexpr (Master_matrix::Option_list::has_map_column_container) { return matrix_.size(); @@ -516,8 +519,7 @@ inline typename Base_matrix::Index Base_matrix::ge template template -inline void Base_matrix::add_to(const Entry_range_or_column_index& sourceColumn, - Index targetColumnIndex) +inline void Base_matrix::add_to(const Entry_range_or_column_index& sourceColumn, Index targetColumnIndex) { if constexpr (std::is_integral_v) { _get_column(targetColumnIndex) += _get_column(sourceColumn); @@ -530,7 +532,7 @@ template template inline void Base_matrix::multiply_target_and_add_to(const Entry_range_or_column_index& sourceColumn, const Field_element& coefficient, - Index targetColumnIndex) + Index targetColumnIndex) { if constexpr (std::is_integral_v) { _get_column(targetColumnIndex).multiply_target_and_add(coefficient, _get_column(sourceColumn)); @@ -543,7 +545,7 @@ template template inline void Base_matrix::multiply_source_and_add_to(const Field_element& coefficient, const Entry_range_or_column_index& sourceColumn, - Index targetColumnIndex) + Index targetColumnIndex) { if constexpr (std::is_integral_v) { _get_column(targetColumnIndex).multiply_source_and_add(_get_column(sourceColumn), coefficient); @@ -553,31 +555,34 @@ inline void Base_matrix::multiply_source_and_add_to(const Field_e } template -inline void Base_matrix::zero_entry(Index columnIndex, Index rowIndex) +inline void Base_matrix::zero_entry(Index columnIndex, Index rowIndex) { _get_column(columnIndex).clear(_get_real_row_index(rowIndex)); } template -inline void Base_matrix::zero_column(Index columnIndex) { +inline void Base_matrix::zero_column(Index columnIndex) +{ _get_column(columnIndex).clear(); } template -inline bool Base_matrix::is_zero_entry(Index columnIndex, Index rowIndex) const +inline bool Base_matrix::is_zero_entry(Index columnIndex, Index rowIndex) const { return !(_get_column(columnIndex).is_non_zero(_get_real_row_index(rowIndex))); } template -inline bool Base_matrix::is_zero_column(Index columnIndex) +inline bool Base_matrix::is_zero_column(Index columnIndex) { return _get_column(columnIndex).is_empty(); } template -inline Base_matrix& Base_matrix::operator=(const Base_matrix& other) +inline Base_matrix& Base_matrix::operator=(const Base_matrix& other) { + if (this == &other) return *this; + Swap_opt::operator=(other); RA_opt::operator=(other); matrix_.clear(); @@ -585,8 +590,8 @@ inline Base_matrix& Base_matrix::operator=(const B colSettings_ = other.colSettings_; matrix_.reserve(other.matrix_.size()); - for (const auto& cont : other.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : other.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -597,32 +602,40 @@ inline Base_matrix& Base_matrix::operator=(const B } template -inline void Base_matrix::print(Index startCol, Index endCol, Index startRow, Index endRow) +inline Base_matrix& Base_matrix::operator=(Base_matrix&& other) noexcept +{ + Swap_opt::operator=(std::move(other)); + RA_opt::operator=(std::move(other)); + + matrix_ = std::move(other.matrix_); + nextInsertIndex_ = std::exchange(other.nextInsertIndex_, 0); + colSettings_ = std::exchange(other.colSettings_, nullptr); + + return *this; +} + +template +inline void Base_matrix::print() { _orderRowsIfNecessary(); - if (endCol == static_cast(-1)) endCol = nextInsertIndex_; - if (endRow == static_cast(-1)) endRow = nextInsertIndex_; std::cout << "Base_matrix:\n"; - for (Index i = startCol; i < endCol && i < nextInsertIndex_; ++i) { + for (Index i = 0; i < nextInsertIndex_; ++i) { const Column& col = matrix_[i]; - auto cont = col.get_content(endRow); - for (Index j = startRow; j < endRow; ++j) { - if (cont[j] == 0u) + for (const auto& e : col.get_content(nextInsertIndex_)) { + if (e == 0U) std::cout << "- "; else - std::cout << cont[j] << " "; + std::cout << e << " "; } std::cout << "\n"; } std::cout << "\n"; if constexpr (Master_matrix::Option_list::has_row_access) { std::cout << "Row Matrix:\n"; - for (Index i = startRow; i < endRow && i < nextInsertIndex_; ++i) { - const auto& row = (*RA_opt::rows_)[i]; - auto it = row.begin(); - std::advance(it, startCol); - for (; it != row.end() && startCol < endCol; ++it, ++startCol) { - std::cout << it->get_column_index() << " "; + for (Index i = 0; i < nextInsertIndex_; ++i) { + const auto& row = RA_opt::get_row(i); + for (const auto& entry : row) { + std::cout << entry.get_column_index() << " "; } std::cout << "(" << i << ")\n"; } @@ -632,25 +645,25 @@ inline void Base_matrix::print(Index startCol, Index endCol, Inde template template -inline void Base_matrix::_insert(const Container& column, Index columnIndex, Dimension dim) +inline void Base_matrix::_insert(const Container& column, Index columnIndex, Dimension dim) { _orderRowsIfNecessary(); - //resize of containers when necessary: + // resize of containers when necessary: Index pivot = 0; if (column.begin() != column.end()) { - //first, compute pivot of `column` + // first, compute pivot of `column` if constexpr (Master_matrix::Option_list::is_z2) { pivot = *std::prev(column.end()); } else { pivot = std::prev(column.end())->first; } - //row container + // row container if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) - if (RA_opt::rows_->size() <= pivot) RA_opt::rows_->resize(pivot + 1); + RA_opt::_resize(pivot); } - //row swap map containers + // row swap map containers if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { for (auto id : column) { @@ -660,21 +673,14 @@ inline void Base_matrix::_insert(const Container& column, Index c } else { idx = id.first; } - Swap_opt::indexToRow_[idx] = idx; - Swap_opt::rowToIndex_[idx] = idx; + Swap_opt::_initialize_row_index(idx); } } } else { if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { - Index size = Swap_opt::indexToRow_.size(); - if (size <= pivot) { - for (Index i = size; i <= pivot; i++) { - Swap_opt::indexToRow_.push_back(i); - Swap_opt::rowToIndex_.push_back(i); - } - } + Swap_opt::_initialize_row_index(pivot); } - //column container + // column container if constexpr (!Master_matrix::Option_list::has_row_access) { if (matrix_.size() <= columnIndex) { matrix_.resize(columnIndex + 1); @@ -686,10 +692,10 @@ inline void Base_matrix::_insert(const Container& column, Index c } template -inline void Base_matrix::_orderRowsIfNecessary() +inline void Base_matrix::_orderRowsIfNecessary() { if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { - if (Swap_opt::rowSwapped_) Swap_opt::_orderRows(); + if (Swap_opt::_row_were_swapped()) Swap_opt::_orderRows(); } } @@ -718,11 +724,7 @@ template inline typename Base_matrix::Index Base_matrix::_get_real_row_index(Index rowIndex) const { if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { - if constexpr (Master_matrix::Option_list::has_map_column_container) { - return Swap_opt::indexToRow_.at(rowIndex); - } else { - return Swap_opt::indexToRow_[rowIndex]; - } + return Swap_opt::_get_row_index(rowIndex); } else { return rowIndex; } @@ -730,16 +732,17 @@ inline typename Base_matrix::Index Base_matrix::_g template template -inline void Base_matrix::_container_insert(const Container& column, Index pos, Dimension dim){ +inline void Base_matrix::_container_insert(const Container& column, Index pos, Dimension dim) +{ if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, dim, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(pos, column, dim, RA_opt::rows_, colSettings_); + matrix_.emplace_back(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_); } else { matrix_[pos] = Column(column, dim, colSettings_); } @@ -747,16 +750,17 @@ inline void Base_matrix::_container_insert(const Container& colum } template -inline void Base_matrix::_container_insert(const Column& column, [[maybe_unused]] Index pos){ +inline void Base_matrix::_container_insert(const Column& column, [[maybe_unused]] Index pos) +{ if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(column, column.get_column_index(), RA_opt::rows_, colSettings_); + matrix_.emplace_back(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_); } else { matrix_.emplace_back(column, colSettings_); } diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h b/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h index cdb3efb6..444a89e3 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Base_matrix_with_column_compression.h @@ -17,9 +17,9 @@ #ifndef PM_BASE_MATRIX_COMPRESSION_H #define PM_BASE_MATRIX_COMPRESSION_H -#include //print() only +#include //print() only #include -#include //std::swap, std::move & std::exchange +#include //std::swap, std::move & std::exchange #include #include @@ -38,67 +38,97 @@ namespace persistence_matrix { * identical columns in the matrix are compressed together as the same column. For matrices with a lot of redundant * columns, this will save a lot of space. Also, any addition made onto a column will be performed at the same time * on all other identical columns, which is an advantage for the cohomology algorithm for example. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_access_option +class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_access_option { public: - using Index = typename Master_matrix::Index; /**< Container index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Index = typename Master_matrix::Index; /**< Container index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Field element value type. */ - using Row = typename Master_matrix::Row; /**< Row type, - only necessary with row access option. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to - necessary external classes. */ + using Field_element = typename Master_matrix::Element; /**< Field element value type. */ + using Row = typename Master_matrix::Row; /**< Row type, + only necessary with row access option. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ /** * @brief Type for columns. Only one for each "column class" is explicitly constructed. */ - class Column - : public Master_matrix::Column, - public boost::intrusive::set_base_hook > + class Column : public Master_matrix::Column, + public boost::intrusive::set_base_hook > { public: using Base = typename Master_matrix::Column; - Column(Column_settings* colSettings = nullptr) - : Base(colSettings) {} + Column(Column_settings* colSettings = nullptr) : Base(colSettings) {} + template - Column(const Container& nonZeroRowIndices, Column_settings* colSettings) - : Base(nonZeroRowIndices, colSettings) {} + Column(const Container& nonZeroRowIndices, Column_settings* colSettings) : Base(nonZeroRowIndices, colSettings) + {} + template - Column(Index columnIndex, const Container& nonZeroRowIndices, Row_container& rowContainer, - Column_settings* colSettings) - : Base(columnIndex, nonZeroRowIndices, rowContainer, colSettings) {} + Column(Index columnIndex, + const Container& nonZeroRowIndices, + Row_container* rowContainer, + Column_settings* colSettings) + : Base(columnIndex, nonZeroRowIndices, rowContainer, colSettings) + {} + template Column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings) - : Base(nonZeroRowIndices, dimension, colSettings) {} + : Base(nonZeroRowIndices, dimension, colSettings) + {} + template - Column(Index columnIndex, const Container& nonZeroRowIndices, Dimension dimension, - Row_container& rowContainer, Column_settings* colSettings) - : Base(columnIndex, nonZeroRowIndices, dimension, rowContainer, colSettings) {} + Column(Index columnIndex, + const Container& nonZeroRowIndices, + Dimension dimension, + Row_container* rowContainer, + Column_settings* colSettings) + : Base(columnIndex, nonZeroRowIndices, dimension, rowContainer, colSettings) + {} + Column(const Column& column, Column_settings* colSettings = nullptr) - : Base(static_cast(column), colSettings) {} + : Base(static_cast(column), colSettings), rep_(column.rep_) + {} + template - Column(const Column& column, Index columnIndex, Row_container& rowContainer, - Column_settings* colSettings = nullptr) - : Base(static_cast(column), columnIndex, rowContainer, colSettings) {} - Column(Column&& column) noexcept : Base(std::move(static_cast(column))) {} + Column(const Column& column, Index columnIndex, Row_container* rowContainer, Column_settings* colSettings = nullptr) + : Base(static_cast(column), columnIndex, rowContainer, colSettings), rep_(column.rep_) + {} + + Column(Column&& column) noexcept : Base(std::move(static_cast(column))), rep_(std::exchange(column.rep_, 0)) + {} - //TODO: is it possible to make this work? - // template - // Column(U&&... u) : Base(std::forward(u)...) {} + ~Column() = default; + + // TODO: is it possible to make this work? + // template + // Column(U&&... u) : Base(std::forward(u)...) {} Index get_rep() const { return rep_; } + void set_rep(const Index& rep) { rep_ = rep; } + Column& operator=(const Column& other) + { + Base::operator=(other); + rep_ = other.rep_; + } + + Column& operator=(Column&& other) noexcept + { + Base::operator=(std::move(other)); + rep_ = std::exchange(other.rep_, 0); + } + struct Hasher { size_t operator()(const Column& c) const { return std::hash()(static_cast(c)); } }; @@ -109,49 +139,47 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ /** * @brief Constructs an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Base_matrix_with_column_compression(Column_settings* colSettings); /** * @brief Constructs a matrix from the given ordered columns. The columns are inserted in the given order. * If no identical column already existed, a copy of the column is stored. If an identical one existed, no new * column is constructed and the relationship between the two is registered in an union-find structure. - * + * * @tparam Container Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. * @param columns A vector of @ref Matrix::Entry_representative ranges to construct the columns from. * The content of the ranges are assumed to be sorted by increasing ID value. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Base_matrix_with_column_compression(const std::vector& columns, - Column_settings* colSettings); + Base_matrix_with_column_compression(const std::vector& columns, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - Base_matrix_with_column_compression(unsigned int numberOfColumns, - Column_settings* colSettings); + Base_matrix_with_column_compression(unsigned int numberOfColumns, Column_settings* colSettings); /** * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ Base_matrix_with_column_compression(const Base_matrix_with_column_compression& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Base_matrix_with_column_compression(Base_matrix_with_column_compression&& other) noexcept; @@ -162,32 +190,33 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ /** * @brief Inserts a new ordered column at the end of the matrix by copying the given range of - * @ref Matrix::Entry_representative. The content of the range is assumed to be sorted by increasing ID value. - * + * @ref Matrix::Entry_representative. The content of the range is assumed to be sorted by increasing ID value. + * * @tparam Container Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() method. * @param column Range of @ref Matrix::Entry_representative from which the column has to be constructed. Assumed to be - * ordered by increasing ID value. + * ordered by increasing ID value. */ template void insert_column(const Container& column); /** * @brief Same as @ref insert_column, only for interface purposes. The given dimension is ignored and not stored. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Range of @ref Matrix::Entry_representative from which the column has to be constructed. Assumed to - * be ordered by increasing ID value. + * be ordered by increasing ID value. * @param dim Ignored. */ template - void insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. * * Remark: the method it-self is not const, because of the path compression optimization of the union-find structure, - * when a column is looked up. - * + * when a column is looked up. + * * @param columnIndex @ref MatIdx index of the column to return. * @return Const reference to the column. */ @@ -195,9 +224,9 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. * Returns the row at the given @ref rowindex "row index" of the compressed matrix. - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. * Note that the row will be from the compressed matrix, that is, the one with only unique columns. - * + * * @param rowIndex @ref rowindex "Row index" of the row to return. * @return Const reference to the row. */ @@ -207,19 +236,19 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * are true: assumes that the row is empty and removes it. Otherwise, does nothing. * * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row entries are not - * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when * the column entries are destroyed later. The row access is just meant as a "read only" access to the rows and the * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from * dictionaries. This allows to avoid testing the emptiness of a row at each column entry removal, what can be - * quite frequent. - * + * quite frequent. + * * @param rowIndex @ref rowindex "Row index" of the empty row. */ void erase_empty_row(Index rowIndex); /** * @brief Returns the current number of columns in the matrix, counting also the redundant columns. - * + * * @return The number of columns. */ Index get_number_of_columns() const; @@ -229,7 +258,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * * The representatives of redundant columns are summed together, which means that * all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range_or_column_index Either a range of @ref Entry with a begin() and end() method, * or any integer type. * @param sourceColumn Either an entry range or the @ref MatIdx index of the column to add. @@ -243,7 +272,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * * The representatives of redundant columns are summed together, which means that * all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range_or_column_index Either a range of @ref Entry with a begin() and end() method, * or any integer type. * @param sourceColumn Either a @ref Entry range or the @ref MatIdx index of the column to add. @@ -251,7 +280,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * @param targetColumnIndex @ref MatIdx index of the target column. */ template - void multiply_target_and_add_to(const Entry_range_or_column_index& sourceColumn, + void multiply_target_and_add_to(const Entry_range_or_column_index& sourceColumn, const Field_element& coefficient, Index targetColumnIndex); /** @@ -260,7 +289,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * * The representatives of redundant columns are summed together, which means that * all column compressed together with the target column are affected by the change, not only the target. - * + * * @tparam Entry_range_or_column_index Either a range of @ref Entry with a begin() and end() method, * or any integer type. * @param coefficient Value to multiply. @@ -268,13 +297,13 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * @param targetColumnIndex @ref MatIdx index of the target column. */ template - void multiply_source_and_add_to(const Field_element& coefficient, + void multiply_source_and_add_to(const Field_element& coefficient, const Entry_range_or_column_index& sourceColumn, Index targetColumnIndex); /** * @brief Indicates if the entry at given coordinates has value zero. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -283,7 +312,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ bool is_zero_entry(Index columnIndex, Index rowIndex); /** * @brief Indicates if the column at given index has value zero. - * + * * @param columnIndex @ref MatIdx index of the column. * @return true If the column has value zero. * @return false Otherwise. @@ -292,10 +321,11 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ /** * @brief Resets the matrix to an empty matrix. - * + * * @param colSettings Pointer to the entry factory. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { columnToRep_.clear_and_dispose(Delete_disposer(this)); columnClasses_ = boost::disjoint_sets_with_storage<>(); repToColumn_.clear(); @@ -307,10 +337,16 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * @brief Assign operator. */ Base_matrix_with_column_compression& operator=(const Base_matrix_with_column_compression& other); + /** + * @brief Move assign operator. + */ + Base_matrix_with_column_compression& operator=(Base_matrix_with_column_compression&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(Base_matrix_with_column_compression& matrix1, Base_matrix_with_column_compression& matrix2) { + friend void swap(Base_matrix_with_column_compression& matrix1, Base_matrix_with_column_compression& matrix2) noexcept + { matrix1.columnToRep_.swap(matrix2.columnToRep_); std::swap(matrix1.columnClasses_, matrix2.columnClasses_); matrix1.repToColumn_.swap(matrix2.repToColumn_); @@ -353,7 +389,7 @@ class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_ * exchanged instead. */ std::unique_ptr > columnPool_; - inline static const Column empty_column_; /**< Representative for empty columns. */ + inline static const Column empty_column_; /**< Representative for empty columns. */ void _insert_column(Index columnIndex); void _insert_double_column(Index columnIndex, typename Col_dict::iterator& doubleIt); @@ -363,12 +399,14 @@ template inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( Column_settings* colSettings) : RA_opt(), nextColumnIndex_(0), colSettings_(colSettings), columnPool_(new Simple_object_pool) -{} +{ +} template template inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( - const std::vector& columns, Column_settings* colSettings) + const std::vector& columns, + Column_settings* colSettings) : RA_opt(columns.size()), columnClasses_(columns.size()), repToColumn_(columns.size(), nullptr), @@ -383,18 +421,21 @@ inline Base_matrix_with_column_compression::Base_matrix_with_colu template inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( - unsigned int numberOfColumns, Column_settings* colSettings) + unsigned int numberOfColumns, + Column_settings* colSettings) : RA_opt(numberOfColumns), columnClasses_(numberOfColumns), repToColumn_(numberOfColumns, nullptr), nextColumnIndex_(0), colSettings_(colSettings), columnPool_(new Simple_object_pool) -{} +{ +} template inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( - const Base_matrix_with_column_compression& matrixToCopy, Column_settings* colSettings) + const Base_matrix_with_column_compression& matrixToCopy, + Column_settings* colSettings) : RA_opt(static_cast(matrixToCopy)), columnClasses_(matrixToCopy.columnClasses_), repToColumn_(matrixToCopy.repToColumn_.size(), nullptr), @@ -406,7 +447,7 @@ inline Base_matrix_with_column_compression::Base_matrix_with_colu if (col != nullptr) { if constexpr (Master_matrix::Option_list::has_row_access) { repToColumn_[nextColumnIndex_] = - columnPool_->construct(*col, col->get_column_index(), RA_opt::rows_, colSettings_); + columnPool_->construct(*col, col->get_column_index(), RA_opt::_get_rows_ptr(), colSettings_); } else { repToColumn_[nextColumnIndex_] = columnPool_->construct(*col, colSettings_); } @@ -427,17 +468,18 @@ inline Base_matrix_with_column_compression::Base_matrix_with_colu nextColumnIndex_(std::exchange(other.nextColumnIndex_, 0)), colSettings_(std::exchange(other.colSettings_, nullptr)), columnPool_(std::exchange(other.columnPool_, nullptr)) -{} +{ +} template -inline Base_matrix_with_column_compression::~Base_matrix_with_column_compression() +inline Base_matrix_with_column_compression::~Base_matrix_with_column_compression() { columnToRep_.clear_and_dispose(Delete_disposer(this)); } template template -inline void Base_matrix_with_column_compression::insert_column(const Container& column) +inline void Base_matrix_with_column_compression::insert_column(const Container& column) { insert_boundary(column); } @@ -445,12 +487,12 @@ inline void Base_matrix_with_column_compression::insert_column(co template template inline void Base_matrix_with_column_compression::insert_boundary(const Boundary_range& boundary, - Dimension dim) + Dimension dim) { // handles a dimension which is not actually stored. // TODO: verify if this is not a problem for the cohomology algorithm and if yes, - // change how Column_dimension_option is choosen. - if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + // change how Column_dimension_option is chosen. + if (dim == Master_matrix::template get_null_value()) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) { if (boundary.begin() != boundary.end()) { @@ -460,7 +502,7 @@ inline void Base_matrix_with_column_compression::insert_boundary( } else { pivot = std::prev(boundary.end())->first; } - if (RA_opt::rows_->size() <= pivot) RA_opt::rows_->resize(pivot + 1); + RA_opt::_resize(pivot); } } @@ -469,14 +511,14 @@ inline void Base_matrix_with_column_compression::insert_boundary( columnClasses_.link(nextColumnIndex_, nextColumnIndex_); if constexpr (Master_matrix::Option_list::has_row_access) { repToColumn_.push_back( - columnPool_->construct(nextColumnIndex_, boundary, dim, RA_opt::rows_, colSettings_)); + columnPool_->construct(nextColumnIndex_, boundary, dim, RA_opt::_get_rows_ptr(), colSettings_)); } else { repToColumn_.push_back(columnPool_->construct(boundary, dim, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { repToColumn_[nextColumnIndex_] = - columnPool_->construct(nextColumnIndex_, boundary, dim, RA_opt::rows_, colSettings_); + columnPool_->construct(nextColumnIndex_, boundary, dim, RA_opt::_get_rows_ptr(), colSettings_); } else { repToColumn_[nextColumnIndex_] = columnPool_->construct(boundary, dim, colSettings_); } @@ -488,7 +530,7 @@ inline void Base_matrix_with_column_compression::insert_boundary( template inline const typename Base_matrix_with_column_compression::Column& -Base_matrix_with_column_compression::get_column(Index columnIndex) +Base_matrix_with_column_compression::get_column(Index columnIndex) { auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; if (col == nullptr) return empty_column_; @@ -497,7 +539,7 @@ Base_matrix_with_column_compression::get_column(Index columnIndex template inline const typename Base_matrix_with_column_compression::Row& -Base_matrix_with_column_compression::get_row(Index rowIndex) const +Base_matrix_with_column_compression::get_row(Index rowIndex) const { static_assert(Master_matrix::Option_list::has_row_access, "Row access has to be enabled for this method."); @@ -505,7 +547,7 @@ Base_matrix_with_column_compression::get_row(Index rowIndex) cons } template -inline void Base_matrix_with_column_compression::erase_empty_row(Index rowIndex) +inline void Base_matrix_with_column_compression::erase_empty_row(Index rowIndex) { if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { RA_opt::erase_empty_row(rowIndex); @@ -514,7 +556,7 @@ inline void Base_matrix_with_column_compression::erase_empty_row( template inline typename Base_matrix_with_column_compression::Index -Base_matrix_with_column_compression::get_number_of_columns() const +Base_matrix_with_column_compression::get_number_of_columns() const { return nextColumnIndex_; } @@ -522,7 +564,7 @@ Base_matrix_with_column_compression::get_number_of_columns() cons template template inline void Base_matrix_with_column_compression::add_to(const Entry_range_or_column_index& sourceColumn, - Index targetColumnIndex) + Index targetColumnIndex) { // handle case where targetRep == sourceRep? Index targetRep = columnClasses_.find_set(targetColumnIndex); @@ -539,7 +581,9 @@ inline void Base_matrix_with_column_compression::add_to(const Ent template template inline void Base_matrix_with_column_compression::multiply_target_and_add_to( - const Entry_range_or_column_index& sourceColumn, const Field_element& coefficient, Index targetColumnIndex) + const Entry_range_or_column_index& sourceColumn, + const Field_element& coefficient, + Index targetColumnIndex) { // handle case where targetRep == sourceRep? Index targetRep = columnClasses_.find_set(targetColumnIndex); @@ -556,7 +600,9 @@ inline void Base_matrix_with_column_compression::multiply_target_ template template inline void Base_matrix_with_column_compression::multiply_source_and_add_to( - const Field_element& coefficient, const Entry_range_or_column_index& sourceColumn, Index targetColumnIndex) + const Field_element& coefficient, + const Entry_range_or_column_index& sourceColumn, + Index targetColumnIndex) { // handle case where targetRep == sourceRep? Index targetRep = columnClasses_.find_set(targetColumnIndex); @@ -571,7 +617,7 @@ inline void Base_matrix_with_column_compression::multiply_source_ } template -inline bool Base_matrix_with_column_compression::is_zero_entry(Index columnIndex, Index rowIndex) +inline bool Base_matrix_with_column_compression::is_zero_entry(Index columnIndex, Index rowIndex) { auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; if (col == nullptr) return true; @@ -579,7 +625,7 @@ inline bool Base_matrix_with_column_compression::is_zero_entry(In } template -inline bool Base_matrix_with_column_compression::is_zero_column(Index columnIndex) +inline bool Base_matrix_with_column_compression::is_zero_column(Index columnIndex) { auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; if (col == nullptr) return true; @@ -588,8 +634,10 @@ inline bool Base_matrix_with_column_compression::is_zero_column(I template inline Base_matrix_with_column_compression& -Base_matrix_with_column_compression::operator=(const Base_matrix_with_column_compression& other) +Base_matrix_with_column_compression::operator=(const Base_matrix_with_column_compression& other) { + if (this == &other) return *this; + for (auto col : repToColumn_) { if (col != nullptr) { columnPool_->destroy(col); @@ -605,7 +653,7 @@ Base_matrix_with_column_compression::operator=(const Base_matrix_ for (const Column* col : other.repToColumn_) { if constexpr (Master_matrix::Option_list::has_row_access) { repToColumn_[nextColumnIndex_] = - columnPool_->construct(*col, col->get_column_index(), RA_opt::rows_, colSettings_); + columnPool_->construct(*col, col->get_column_index(), RA_opt::_get_rows_ptr(), colSettings_); } else { repToColumn_[nextColumnIndex_] = columnPool_->construct(*col, colSettings_); } @@ -617,12 +665,32 @@ Base_matrix_with_column_compression::operator=(const Base_matrix_ } template -inline void Base_matrix_with_column_compression::print() +inline Base_matrix_with_column_compression& +Base_matrix_with_column_compression::operator=(Base_matrix_with_column_compression&& other) noexcept +{ + if (repToColumn_ == &(other.repToColumn_)) return *this; + + RA_opt::operator=(std::move(other)); + + columnToRep_.clear_and_dispose(Delete_disposer(this)); + + columnToRep_ = std::move(other.columnToRep_); + columnClasses_ = std::move(other.columnClasses_); + repToColumn_ = std::move(other.repToColumn_); + nextColumnIndex_ = std::exchange(other.nextColumnIndex_, 0); + colSettings_ = std::exchange(other.colSettings_, nullptr); + columnPool_ = std::exchange(other.columnPool_, nullptr); + + return *this; +} + +template +inline void Base_matrix_with_column_compression::print() { std::cout << "Compressed_matrix:\n"; for (Column& col : columnToRep_) { for (auto e : col->get_content(nextColumnIndex_)) { - if (e == 0u) + if (e == 0U) std::cout << "- "; else std::cout << e << " "; @@ -635,8 +703,8 @@ inline void Base_matrix_with_column_compression::print() } std::cout << "\n"; std::cout << "Row Matrix:\n"; - for (Index i = 0; i < RA_opt::rows_->size(); ++i) { - const Row& row = RA_opt::rows_[i]; + for (Index i = 0; i < RA_opt::_get_rows_ptr()->size(); ++i) { + const Row& row = RA_opt::get_row(i); for (const auto& entry : row) { std::cout << entry.get_column_index() << " "; } @@ -646,7 +714,7 @@ inline void Base_matrix_with_column_compression::print() } template -inline void Base_matrix_with_column_compression::_insert_column(Index columnIndex) +inline void Base_matrix_with_column_compression::_insert_column(Index columnIndex) { Column& col = *repToColumn_[columnIndex]; @@ -658,14 +726,15 @@ inline void Base_matrix_with_column_compression::_insert_column(I col.set_rep(columnIndex); auto res = columnToRep_.insert(col); - if (res.first->get_rep() != columnIndex) { //if true, then redundant column + if (res.first->get_rep() != columnIndex) { // if true, then redundant column _insert_double_column(columnIndex, res.first); } } template inline void Base_matrix_with_column_compression::_insert_double_column( - Index columnIndex, typename Col_dict::iterator& doubleIt) + Index columnIndex, + typename Col_dict::iterator& doubleIt) { Index doubleRep = doubleIt->get_rep(); columnClasses_.link(columnIndex, doubleRep); // both should be representatives diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h b/multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h index 3330d02a..7296c9e1 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Boundary_matrix.h @@ -18,8 +18,7 @@ #define PM_BOUNDARY_MATRIX_H #include -#include //print() only -#include +#include //print() only #include #include //std::swap, std::move & std::exchange @@ -34,94 +33,108 @@ namespace persistence_matrix { * @brief %Matrix structure to store the ordered @ref boundarymatrix "boundary matrix" \f$ R \f$ of a filtered complex * in order to compute its persistent homology. Provides an access to its columns and rows as well as the possibility * to remove the last cells of the filtration while maintaining a valid barcode. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template class Boundary_matrix : public Master_matrix::Matrix_dimension_option, public Master_matrix::template Base_swap_option >, public Master_matrix::Base_pairing_option, - public Master_matrix::Matrix_row_access_option + protected Master_matrix::Matrix_row_access_option { + private: + using Dim_opt = typename Master_matrix::Matrix_dimension_option; + using Swap_opt = typename Master_matrix::template Base_swap_option >; + using Pair_opt = typename Master_matrix::Base_pairing_option; + using RA_opt = typename Master_matrix::Matrix_row_access_option; + + static constexpr bool activeDimOption_ = + Master_matrix::Option_list::has_matrix_maximal_dimension_access || Master_matrix::maxDimensionIsNeeded; + static constexpr bool activeSwapOption_ = + Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update; + static constexpr bool activePairingOption_ = Master_matrix::Option_list::has_column_pairings && + !Master_matrix::Option_list::has_vine_update && + !Master_matrix::Option_list::can_retrieve_representative_cycles; + public: - using Index = typename Master_matrix::Index; /**< Container index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Index = typename Master_matrix::Index; /**< Container index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ - using Row = typename Master_matrix::Row; /**< Row type, - only necessary with row access option. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to - necessary external classes. */ + using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ + using Row = typename Master_matrix::Row; /**< Row type, + only necessary with row access option. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ /** * @brief Constructs an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Boundary_matrix(Column_settings* colSettings); /** * @brief Constructs a new matrix from the given ranges of @ref Matrix::Entry_representative. Each range corresponds * to a column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by * increasing IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting - * with 0. - * + * with 0. + * * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the * @ref Boundary_matrix(unsigned int numberOfColumns, Column_settings* colSettings) * constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Boundary_matrix(const std::vector& orderedBoundaries, - Column_settings* colSettings); + Boundary_matrix(const std::vector& orderedBoundaries, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Boundary_matrix(unsigned int numberOfColumns, Column_settings* colSettings); /** * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ - Boundary_matrix(const Boundary_matrix& matrixToCopy, - Column_settings* colSettings = nullptr); + Boundary_matrix(const Boundary_matrix& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Boundary_matrix(Boundary_matrix&& other) noexcept; + ~Boundary_matrix() = default; + /** - * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. - * This means that it is assumed that this method is called on boundaries in the order of the filtration. - * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, * starting at 0. If it is not the case, use the other * @ref insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim) "insert_boundary" * instead by indicating the cell ID used in the boundaries when the cell is inserted. @@ -133,16 +146,17 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * is requested in order to apply some optimizations with the additional knowledge. Hence, the barcode will also * not be updated, so call @ref Base_pairing::get_current_barcode "get_current_barcode" only when the matrix is * complete. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Boundary generating the new column. The content should be ordered by ID. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. * @return The @ref MatIdx index of the inserted boundary. */ template - Index insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + Index insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief It does the same as the other version, but allows the boundary cells to be identified without restrictions * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then @@ -150,27 +164,29 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * * As a cell has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that * the cells are inserted by order of filtration), it is sufficient to indicate the ID of the cell being inserted. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param cellIndex @ref IDIdx index to use to identify the new cell. - * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the - * @p cellIndex values of precedent calls of the method for the corresponding cells and should be ordered in + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p cellIndex values of precedent calls of the method for the corresponding cells and should be ordered in * increasing order. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. * @return The @ref MatIdx index of the inserted boundary. */ template - Index insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim = -1); + Index insert_boundary(ID_index cellIndex, + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. * * Note that before returning the column, all column entries can eventually be reordered, if lazy swaps occurred. * It is therefore recommended to avoid calling @ref get_column between column or row swaps, otherwise the benefits * of the the laziness is lost. - * + * * @param columnIndex @ref MatIdx index of the column to return. * @return Reference to the column. */ @@ -178,12 +194,12 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. * Returns the row at the given @ref rowindex "row index" of the matrix. - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. * * Note that before returning the row, all column entries can eventually be reordered, if lazy swaps occurred. * It is therefore recommended to avoid calling @ref get_row between column or row swaps, otherwise the benefits * of the the laziness is lost. - * + * * @param rowIndex @ref rowindex "Row index" of the row to return. * @return Reference to the row. */ @@ -191,7 +207,7 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true. * Removes the last cell in the filtration from the matrix and updates the barcode if this one was already computed. - * + * * @return The pivot of the removed cell. */ Index remove_last(); @@ -202,26 +218,26 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * Otherwise, does nothing. * * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row entries are not - * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when * the column entries are destroyed later. The row access is just meant as a "read only" access to the rows and the * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from * dictionaries. This allows to avoid testing the emptiness of a row at each column entry removal, what can be - * quite frequent. - * + * quite frequent. + * * @param rowIndex @ref rowindex "Row index" of the empty row. */ void erase_empty_row(Index rowIndex); /** * @brief Returns the current number of columns in the matrix. - * + * * @return The number of columns. */ Index get_number_of_columns() const; /** * @brief Returns the dimension of the given column. - * + * * @param columnIndex @ref MatIdx index of the column representing the cell. * @return Dimension of the cell. */ @@ -233,7 +249,7 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param sourceColumnIndex @ref MatIdx index of the source column. * @param targetColumnIndex @ref MatIdx index of the target column. */ @@ -245,14 +261,12 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param sourceColumnIndex @ref MatIdx index of the source column. * @param coefficient Value to multiply. * @param targetColumnIndex @ref MatIdx index of the target column. */ - void multiply_target_and_add_to(Index sourceColumnIndex, - const Field_element& coefficient, - Index targetColumnIndex); + void multiply_target_and_add_to(Index sourceColumnIndex, const Field_element& coefficient, Index targetColumnIndex); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. @@ -260,21 +274,19 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param coefficient Value to multiply. * @param sourceColumnIndex @ref MatIdx index of the source column. * @param targetColumnIndex @ref MatIdx index of the target column. */ - void multiply_source_and_add_to(const Field_element& coefficient, - Index sourceColumnIndex, - Index targetColumnIndex); + void multiply_source_and_add_to(const Field_element& coefficient, Index sourceColumnIndex, Index targetColumnIndex); /** * @brief Zeroes the entry at the given coordinates. * * @warning They will be no verification to ensure that the zeroing makes sense for the validity of a * boundary matrix of a filtered complex. So should be used while knowing what one is doing. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. */ @@ -284,13 +296,13 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * * @warning They will be no verification to ensure that the zeroing makes sense for the validity of a * boundary matrix of a filtered complex. So should be used while knowing what one is doing. - * + * * @param columnIndex @ref MatIdx index of the column to zero. */ void zero_column(Index columnIndex); /** * @brief Indicates if the entry at given coordinates has value zero. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -299,7 +311,7 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, bool is_zero_entry(Index columnIndex, Index rowIndex) const; /** * @brief Indicates if the column at given index has value zero. - * + * * @param columnIndex @ref MatIdx index of the column. * @return true If the column has value zero. * @return false Otherwise. @@ -308,7 +320,7 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Returns the pivot of the given column. - * + * * @param columnIndex @ref MatIdx index of the column. * @return Pivot of the column at @p columnIndex. */ @@ -316,11 +328,15 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Resets the matrix to an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { + if constexpr (activeDimOption_) Dim_opt::_reset(); + if constexpr (activeSwapOption_) Swap_opt::_reset(); + if constexpr (activePairingOption_) Pair_opt::_reset(); matrix_.clear(); nextInsertIndex_ = 0; colSettings_ = colSettings; @@ -330,49 +346,39 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, * @brief Assign operator. */ Boundary_matrix& operator=(const Boundary_matrix& other); + /** + * @brief Move assign operator. + */ + Boundary_matrix& operator=(Boundary_matrix&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(Boundary_matrix& matrix1, Boundary_matrix& matrix2) { - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast >&>(matrix1), - static_cast >&>(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); + friend void swap(Boundary_matrix& matrix1, Boundary_matrix& matrix2) noexcept + { + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); matrix1.matrix_.swap(matrix2.matrix_); std::swap(matrix1.nextInsertIndex_, matrix2.nextInsertIndex_); std::swap(matrix1.colSettings_, matrix2.colSettings_); if constexpr (Master_matrix::Option_list::has_row_access) { - swap(static_cast(matrix1), - static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); } } - void print(Index startCol = 0, Index endCol = -1, Index startRow = 0, Index endRow = -1); // for debug + void print(); // for debug private: - using Dim_opt = typename Master_matrix::Matrix_dimension_option; - using Swap_opt = typename Master_matrix::template Base_swap_option >; - using Pair_opt = typename Master_matrix::Base_pairing_option; - using RA_opt = typename Master_matrix::Matrix_row_access_option; using Column_container = typename Master_matrix::Column_container; friend Swap_opt; friend Pair_opt; - Column_container matrix_; /**< Column container. */ - Index nextInsertIndex_; /**< Next unused column index. */ - Column_settings* colSettings_; /**< Entry factory. */ - - static constexpr bool activeDimOption = - Master_matrix::Option_list::has_matrix_maximal_dimension_access || Master_matrix::maxDimensionIsNeeded; - static constexpr bool activeSwapOption = - Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update; - static constexpr bool activePairingOption = Master_matrix::Option_list::has_column_pairings && - !Master_matrix::Option_list::has_vine_update && - !Master_matrix::Option_list::can_retrieve_representative_cycles; + Column_container matrix_; /**< Column container. */ + Index nextInsertIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Entry factory. */ void _orderRowsIfNecessary(); const Column& _get_column(Index columnIndex) const; @@ -385,7 +391,7 @@ class Boundary_matrix : public Master_matrix::Matrix_dimension_option, template inline Boundary_matrix::Boundary_matrix(Column_settings* colSettings) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Swap_opt(), Pair_opt(), RA_opt(), @@ -397,7 +403,7 @@ template template inline Boundary_matrix::Boundary_matrix(const std::vector& orderedBoundaries, Column_settings* colSettings) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Swap_opt(orderedBoundaries.size()), Pair_opt(), RA_opt(orderedBoundaries.size()), @@ -412,9 +418,8 @@ inline Boundary_matrix::Boundary_matrix(const std::vector -inline Boundary_matrix::Boundary_matrix(unsigned int numberOfColumns, - Column_settings* colSettings) - : Dim_opt(-1), +inline Boundary_matrix::Boundary_matrix(unsigned int numberOfColumns, Column_settings* colSettings) + : Dim_opt(Master_matrix::template get_null_value()), Swap_opt(numberOfColumns), Pair_opt(), RA_opt(numberOfColumns), @@ -429,18 +434,18 @@ inline Boundary_matrix::Boundary_matrix(unsigned int numberOfColu } template -inline Boundary_matrix::Boundary_matrix(const Boundary_matrix& matrixToCopy, +inline Boundary_matrix::Boundary_matrix(const Boundary_matrix& matrixToCopy, Column_settings* colSettings) : Dim_opt(static_cast(matrixToCopy)), Swap_opt(static_cast(matrixToCopy)), Pair_opt(static_cast(matrixToCopy)), RA_opt(static_cast(matrixToCopy)), nextInsertIndex_(matrixToCopy.nextInsertIndex_), - colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) + colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) { matrix_.reserve(matrixToCopy.matrix_.size()); - for (const auto& cont : matrixToCopy.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : matrixToCopy.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -456,60 +461,52 @@ inline Boundary_matrix::Boundary_matrix(Boundary_matrix&& other) RA_opt(std::move(static_cast(other))), matrix_(std::move(other.matrix_)), nextInsertIndex_(std::exchange(other.nextInsertIndex_, 0)), - colSettings_(std::exchange(other.colSettings_, nullptr)) -{} + colSettings_(std::exchange(other.colSettings_, nullptr)) +{ +} template template inline typename Boundary_matrix::Index Boundary_matrix::insert_boundary( - const Boundary_range& boundary, Dimension dim) + const Boundary_range& boundary, + Dimension dim) { return insert_boundary(nextInsertIndex_, boundary, dim); } template template -inline typename Boundary_matrix::Index Boundary_matrix::insert_boundary( - ID_index cellIndex, const Boundary_range& boundary, Dimension dim) +inline typename Boundary_matrix::Index +Boundary_matrix::insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim) { - if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + if (dim == Master_matrix::template get_null_value()) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; _orderRowsIfNecessary(); - //updates container sizes + // updates container sizes if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) { - if (boundary.size() != 0){ + if (boundary.size() != 0) { ID_index pivot; if constexpr (Master_matrix::Option_list::is_z2) { pivot = *std::prev(boundary.end()); } else { pivot = std::prev(boundary.end())->first; } - //row container - if (RA_opt::rows_->size() <= pivot) RA_opt::rows_->resize(pivot + 1); + // row container + RA_opt::_resize(pivot); } } - //row swap map containers - if constexpr (Master_matrix::Option_list::has_map_column_container) { - if constexpr (activeSwapOption) { - Swap_opt::indexToRow_.emplace(cellIndex, cellIndex); - Swap_opt::rowToIndex_.emplace(cellIndex, cellIndex); - } - } else { - if constexpr (activeSwapOption) { - for (Index i = Swap_opt::indexToRow_.size(); i <= cellIndex; ++i) { - Swap_opt::indexToRow_.push_back(i); - Swap_opt::rowToIndex_.push_back(i); - } - } + // row swap map containers + if constexpr (activeSwapOption_) { + Swap_opt::_initialize_row_index(cellIndex); } - //maps for possible shifting between column content and position indices used for birth events - if constexpr (activePairingOption){ - if (cellIndex != nextInsertIndex_){ - Pair_opt::idToPosition_.emplace(cellIndex, nextInsertIndex_); - if constexpr (Master_matrix::Option_list::has_removable_columns){ + // maps for possible shifting between column content and position indices used for birth events + if constexpr (activePairingOption_) { + if (cellIndex != nextInsertIndex_) { + Pair_opt::_insert_id_position(cellIndex, nextInsertIndex_); + if constexpr (Master_matrix::Option_list::has_removable_columns) { Pair_opt::PIDM::map_.emplace(nextInsertIndex_, cellIndex); } } @@ -521,7 +518,7 @@ inline typename Boundary_matrix::Index Boundary_matrix -inline typename Boundary_matrix::Column& Boundary_matrix::get_column(Index columnIndex) +inline typename Boundary_matrix::Column& Boundary_matrix::get_column(Index columnIndex) { _orderRowsIfNecessary(); @@ -529,7 +526,7 @@ inline typename Boundary_matrix::Column& Boundary_matrix -inline typename Boundary_matrix::Row& Boundary_matrix::get_row(Index rowIndex) +inline typename Boundary_matrix::Row& Boundary_matrix::get_row(Index rowIndex) { static_assert(Master_matrix::Option_list::has_row_access, "'get_row' is not implemented for the chosen options."); @@ -539,27 +536,27 @@ inline typename Boundary_matrix::Row& Boundary_matrix -inline typename Boundary_matrix::Index Boundary_matrix::remove_last() +inline typename Boundary_matrix::Index Boundary_matrix::remove_last() { static_assert(Master_matrix::Option_list::has_removable_columns, "'remove_last' is not implemented for the chosen options."); - if (nextInsertIndex_ == 0) return -1; // empty matrix + if (nextInsertIndex_ == 0) return Master_matrix::template get_null_value(); // empty matrix --nextInsertIndex_; - //updates dimension max - if constexpr (activeDimOption) { - Dim_opt::update_down(matrix_.at(nextInsertIndex_).get_dimension()); + // updates dimension max + if constexpr (activeDimOption_) { + Dim_opt::_update_down(matrix_.at(nextInsertIndex_).get_dimension()); } - //computes pivot and removes column from matrix_ + // computes pivot and removes column from matrix_ ID_index pivot; if constexpr (Master_matrix::Option_list::has_map_column_container) { auto it = matrix_.find(nextInsertIndex_); pivot = it->second.get_pivot(); - if constexpr (activeSwapOption) { + if constexpr (activeSwapOption_) { // if the removed column is positive, the pivot won't change value - if (Swap_opt::rowSwapped_ && pivot != static_cast(-1)) { + if (Swap_opt::_row_were_swapped() && pivot != Master_matrix::template get_null_value()) { Swap_opt::_orderRows(); pivot = it->second.get_pivot(); } @@ -567,9 +564,9 @@ inline typename Boundary_matrix::Index Boundary_matrix(-1)) { + if (Swap_opt::_row_were_swapped() && pivot != Master_matrix::template get_null_value()) { Swap_opt::_orderRows(); pivot = matrix_[nextInsertIndex_].get_pivot(); } @@ -585,8 +582,8 @@ inline typename Boundary_matrix::Index Boundary_matrix::Index Boundary_matrix -inline void Boundary_matrix::erase_empty_row(Index rowIndex) +inline void Boundary_matrix::erase_empty_row(Index rowIndex) { - //computes real row index and erases it if necessary from the row swap map containers + // computes real row index and erases it if necessary from the row swap map containers ID_index rowID = rowIndex; - if constexpr (activeSwapOption) { - if constexpr (Master_matrix::Option_list::has_map_column_container) { - auto it = Swap_opt::indexToRow_.find(rowIndex); - rowID = it->second; - Swap_opt::rowToIndex_.erase(rowID); - Swap_opt::indexToRow_.erase(it); - } else { - rowID = Swap_opt::indexToRow_[rowIndex]; - } + if constexpr (activeSwapOption_) { + rowID = Swap_opt::_erase_row(rowIndex); } if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { @@ -615,7 +605,7 @@ inline void Boundary_matrix::erase_empty_row(Index rowIndex) } template -inline typename Boundary_matrix::Index Boundary_matrix::get_number_of_columns() const +inline typename Boundary_matrix::Index Boundary_matrix::get_number_of_columns() const { if constexpr (Master_matrix::Option_list::has_map_column_container) { return matrix_.size(); @@ -626,13 +616,13 @@ inline typename Boundary_matrix::Index Boundary_matrix inline typename Boundary_matrix::Dimension Boundary_matrix::get_column_dimension( - Index columnIndex) const + Index columnIndex) const { return _get_column(columnIndex).get_dimension(); } template -inline void Boundary_matrix::add_to(Index sourceColumnIndex, Index targetColumnIndex) +inline void Boundary_matrix::add_to(Index sourceColumnIndex, Index targetColumnIndex) { _get_column(targetColumnIndex) += _get_column(sourceColumnIndex); } @@ -640,7 +630,7 @@ inline void Boundary_matrix::add_to(Index sourceColumnIndex, Inde template inline void Boundary_matrix::multiply_target_and_add_to(Index sourceColumnIndex, const Field_element& coefficient, - Index targetColumnIndex) + Index targetColumnIndex) { _get_column(targetColumnIndex).multiply_target_and_add(coefficient, _get_column(sourceColumnIndex)); } @@ -648,37 +638,37 @@ inline void Boundary_matrix::multiply_target_and_add_to(Index sou template inline void Boundary_matrix::multiply_source_and_add_to(const Field_element& coefficient, Index sourceColumnIndex, - Index targetColumnIndex) + Index targetColumnIndex) { _get_column(targetColumnIndex).multiply_source_and_add(_get_column(sourceColumnIndex), coefficient); } template -inline void Boundary_matrix::zero_entry(Index columnIndex, Index rowIndex) +inline void Boundary_matrix::zero_entry(Index columnIndex, Index rowIndex) { _get_column(columnIndex).clear(_get_real_row_index(rowIndex)); } template -inline void Boundary_matrix::zero_column(Index columnIndex) +inline void Boundary_matrix::zero_column(Index columnIndex) { _get_column(columnIndex).clear(); } template -inline bool Boundary_matrix::is_zero_entry(Index columnIndex, Index rowIndex) const +inline bool Boundary_matrix::is_zero_entry(Index columnIndex, Index rowIndex) const { return !(_get_column(columnIndex).is_non_zero(_get_real_row_index(rowIndex))); } template -inline bool Boundary_matrix::is_zero_column(Index columnIndex) +inline bool Boundary_matrix::is_zero_column(Index columnIndex) { return _get_column(columnIndex).is_empty(); } template -inline typename Boundary_matrix::Index Boundary_matrix::get_pivot(Index columnIndex) +inline typename Boundary_matrix::Index Boundary_matrix::get_pivot(Index columnIndex) { _orderRowsIfNecessary(); @@ -686,8 +676,10 @@ inline typename Boundary_matrix::Index Boundary_matrix -inline Boundary_matrix& Boundary_matrix::operator=(const Boundary_matrix& other) +inline Boundary_matrix& Boundary_matrix::operator=(const Boundary_matrix& other) { + if (this == &other) return *this; + Dim_opt::operator=(other); Swap_opt::operator=(other); Pair_opt::operator=(other); @@ -698,8 +690,8 @@ inline Boundary_matrix& Boundary_matrix::operator= colSettings_ = other.colSettings_; matrix_.reserve(other.matrix_.size()); - for (const auto& cont : other.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : other.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -710,34 +702,44 @@ inline Boundary_matrix& Boundary_matrix::operator= } template -inline void Boundary_matrix::print(Index startCol, Index endCol, Index startRow, Index endRow) +inline Boundary_matrix& Boundary_matrix::operator=(Boundary_matrix&& other) noexcept { - if (endCol == static_cast(-1)) endCol = nextInsertIndex_; - if (endRow == static_cast(-1)) endRow = nextInsertIndex_; - if constexpr (activeSwapOption) { - if (Swap_opt::rowSwapped_) Swap_opt::_orderRows(); + Dim_opt::operator=(std::move(other)); + Swap_opt::operator=(std::move(other)); + Pair_opt::operator=(std::move(other)); + RA_opt::operator=(std::move(other)); + + matrix_ = std::move(other.matrix_); + nextInsertIndex_ = std::exchange(other.nextInsertIndex_, 0); + colSettings_ = std::exchange(other.colSettings_, nullptr); + + return *this; +} + +template +inline void Boundary_matrix::print() +{ + if constexpr (activeSwapOption_) { + if (Swap_opt::_row_were_swapped()) Swap_opt::_orderRows(); } std::cout << "Boundary_matrix:\n"; - for (Index i = startCol; i < endCol && i < nextInsertIndex_; ++i) { + for (Index i = 0; i < nextInsertIndex_; ++i) { Column& col = matrix_[i]; - auto cont = col.get_content(endRow); - for (Index j = startRow; j < endRow; ++j) { - if (cont[j] == 0u) + for (auto e : col.get_content(nextInsertIndex_)) { + if (e == 0U) std::cout << "- "; else - std::cout << cont[j] << " "; + std::cout << e << " "; } std::cout << "\n"; } std::cout << "\n"; if constexpr (Master_matrix::Option_list::has_row_access) { std::cout << "Row Matrix:\n"; - for (ID_index i = startRow; i < endRow && i < nextInsertIndex_; ++i) { - const auto& row = (*RA_opt::rows_)[i]; - auto it = row.begin(); - std::advance(it, startCol); - for (; it != row.end() && startCol < endCol; ++it, ++startCol) { - std::cout << it->get_column_index() << " "; + for (ID_index i = 0; i < nextInsertIndex_; ++i) { + const auto& row = RA_opt::get_row(i); + for (const typename Column::Entry& entry : row) { + std::cout << entry.get_column_index() << " "; } std::cout << "(" << i << ")\n"; } @@ -746,10 +748,10 @@ inline void Boundary_matrix::print(Index startCol, Index endCol, } template -inline void Boundary_matrix::_orderRowsIfNecessary() +inline void Boundary_matrix::_orderRowsIfNecessary() { - if constexpr (activeSwapOption) { - if (Swap_opt::rowSwapped_) Swap_opt::_orderRows(); + if constexpr (activeSwapOption_) { + if (Swap_opt::_row_were_swapped()) Swap_opt::_orderRows(); } } @@ -765,8 +767,7 @@ inline const typename Boundary_matrix::Column& Boundary_matrix -inline typename Boundary_matrix::Column& Boundary_matrix::_get_column( - Index columnIndex) +inline typename Boundary_matrix::Column& Boundary_matrix::_get_column(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_map_column_container) { return matrix_.at(columnIndex); @@ -780,11 +781,7 @@ inline typename Boundary_matrix::Index Boundary_matrix::Index Boundary_matrix template -inline void Boundary_matrix::_container_insert(const Container& column, - Index pos, - Dimension dim) +inline void Boundary_matrix::_container_insert(const Container& column, Index pos, Dimension dim) { if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, dim, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(pos, column, dim, RA_opt::rows_, colSettings_); + matrix_.emplace_back(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_); } else { if (matrix_.size() <= pos) { matrix_.emplace_back(column, dim, colSettings_); @@ -813,8 +808,8 @@ inline void Boundary_matrix::_container_insert(const Container& c } } } - if constexpr (activeDimOption) { - Dim_opt::update_up(dim); + if constexpr (activeDimOption_) { + Dim_opt::_update_up(dim); } } @@ -823,13 +818,13 @@ inline void Boundary_matrix::_container_insert(const Column& colu { if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(column, column.get_column_index(), RA_opt::rows_, colSettings_); + matrix_.emplace_back(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_); } else { matrix_.emplace_back(column, colSettings_); } diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h b/multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h index 7611990b..23cdf6f6 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Chain_matrix.h @@ -17,7 +17,7 @@ #ifndef PM_CHAIN_MATRIX_H #define PM_CHAIN_MATRIX_H -#include //print() only +#include //print() only #include #include #include @@ -25,7 +25,8 @@ #include //std::swap, std::move & std::exchange #include //std::sort -#include //friend +#include //friend +#include namespace Gudhi { namespace persistence_matrix { @@ -36,9 +37,9 @@ namespace persistence_matrix { * * @brief %Matrix structure storing a compatible base of a filtered chain complex. See @cite zigzag. * The base is constructed from the boundaries of the cells in the complex. Allows the persistent homology to be - * computed, as well as representative cycles. Supports vineyards (see @cite vineyards) and the removal + * computed, as well as representative cycles. Supports vineyards (see @cite vineyards) and the removal * of maximal cells while maintaining a valid barcode. Provides an access to its columns and rows. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template @@ -46,35 +47,57 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, public Master_matrix::Chain_pairing_option, public Master_matrix::Chain_vine_swap_option, public Master_matrix::Chain_representative_cycles_option, - public Master_matrix::Matrix_row_access_option + public Master_matrix::Matrix_row_access_option, + protected std::conditional_t< + Master_matrix::Option_list::has_vine_update && + (Master_matrix::Option_list::has_column_pairings || + Master_matrix::Option_list::can_retrieve_representative_cycles), + Index_mapper>, + Dummy_index_mapper> { + private: + using Dim_opt = typename Master_matrix::Matrix_dimension_option; + using Pair_opt = typename Master_matrix::Chain_pairing_option; + using Swap_opt = typename Master_matrix::Chain_vine_swap_option; + using Rep_opt = typename Master_matrix::Chain_representative_cycles_option; + using RA_opt = typename Master_matrix::Matrix_row_access_option; + + static constexpr bool hasPivotToPosMap_ = + Master_matrix::Option_list::has_vine_update && (Master_matrix::Option_list::has_column_pairings || + Master_matrix::Option_list::can_retrieve_representative_cycles); + + using Pivot_to_pos_mapper_opt = + std::conditional_t>, + Dummy_index_mapper>; + public: /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Row = typename Master_matrix::Row; /**< Row type, only necessary with row - access option. */ - using Entry = typename Master_matrix::Matrix_entry; /**< @ref Entry "Matrix entry" type. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns - to necessary external classes. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ - using Entry_representative = typename Master_matrix::Entry_representative; /**< %Entry content representative. */ - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Row = typename Master_matrix::Row; /**< Row type, only necessary with row + access option. */ + using Entry = typename Master_matrix::Matrix_entry; /**< @ref Entry "Matrix entry" type. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns + to necessary external classes. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ + using Entry_representative = typename Master_matrix::Entry_representative; /**< %Entry content representative. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Constructs an empty matrix. Only available if @ref PersistenceMatrixOptions::has_column_pairings is * true or @ref PersistenceMatrixOptions::has_vine_update is false. Otherwise, birth and death comparators have * to be provided. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Chain_matrix(Column_settings* colSettings); /** @@ -84,34 +107,33 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * with 0. Only available if @ref PersistenceMatrixOptions::has_column_pairings is true or * @ref PersistenceMatrixOptions::has_vine_update is false. Otherwise, birth and death * comparators have to be provided. - * + * * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the * @ref Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings) * constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Chain_matrix(const std::vector& orderedBoundaries, - Column_settings* colSettings); + Chain_matrix(const std::vector& orderedBoundaries, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. Only available * if @ref PersistenceMatrixOptions::has_column_pairings is true or @ref PersistenceMatrixOptions::has_vine_update * is false. Otherwise, birth and death comparators have to be provided. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings); /** @@ -121,11 +143,11 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -136,7 +158,7 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * columns. */ template - Chain_matrix(Column_settings* colSettings, + Chain_matrix(Column_settings* colSettings, const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** @@ -149,23 +171,23 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the @ref Chain_matrix(unsigned int, Column_settings*, * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -176,8 +198,8 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * columns. */ template - Chain_matrix(const std::vector& orderedBoundaries, - Column_settings* colSettings, + Chain_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings, const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** @@ -187,12 +209,12 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -203,28 +225,29 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * columns. */ template - Chain_matrix(unsigned int numberOfColumns, + Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ - Chain_matrix(const Chain_matrix& matrixToCopy, - Column_settings* colSettings = nullptr); + Chain_matrix(const Chain_matrix& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Chain_matrix(Chain_matrix&& other) noexcept; + ~Chain_matrix() = default; + /** * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. * This means that it is assumed that this method is called on boundaries in the order of the filtration. @@ -238,16 +261,18 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * * When inserted, the given boundary is reduced and from the reduction process, the column is deduced in the form of: * `IDIdx + linear combination of older column IDIdxs`. If the barcode is stored, it will be updated. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Boundary generating the new column. The content should be ordered by ID. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. * @return The @ref MatIdx indices of the unpaired chains used to reduce the boundary. */ template - std::vector insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + std::vector insert_boundary( + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief It does the same as the other version, but allows the boundary cells to be identified without restrictions * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then @@ -255,33 +280,34 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * * As a cell has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that * the cells are inserted by order of filtration), it is sufficient to indicate the ID of the cell being inserted. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param cellID @ref IDIdx index to use to identify the new cell. - * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the - * @p cellID values of precedent calls of the method for the corresponding cells and should be ordered in + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p cellID values of precedent calls of the method for the corresponding cells and should be ordered in * increasing order. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. * @return The @ref MatIdx index of the inserted boundary. */ template - std::vector insert_boundary(ID_index cellID, - const Boundary_range& boundary, - Dimension dim = -1); + std::vector insert_boundary( + ID_index cellID, + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param columnIndex @ref MatIdx index of the column to return. * @return Reference to the column. */ Column& get_column(Index columnIndex); /** * @brief Returns the column at the given @ref MatIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param columnIndex @ref MatIdx index of the column to return. * @return Const reference to the column. */ @@ -295,11 +321,11 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * The maximality of the cell is not verified. * Also updates the barcode if it is stored. * - * Note that using the other version of the method could perform better depending on how the data is + * Note that using the other version of the method could perform better depending on how the data is * maintained on the side of the user, that is, if providing the second parameter is easy. * * See also @ref remove_last. - * + * * @param cellID @ref IDIdx index of the cell to remove */ void remove_maximal_cell(ID_index cellID); @@ -312,7 +338,7 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * The maximality of the cell is not verified. * Also updates the barcode if it is stored. * - * To maintain the compatibility, vine swaps are done to move the cell up to the end of the filtration. Once at + * To maintain the compatibility, vine swaps are done to move the cell up to the end of the filtration. Once at * the end, the removal is trivial. But for @ref chainmatrix "chain matrices", swaps do not actually swap the position * of the column every time, so the cells appearing after @p cellID in the filtration have to be searched first within * the matrix. If the user has an easy access to the @ref IDIdx of the cells in the order of filtration, passing them @@ -322,7 +348,7 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * "remove_maximal_cell(cellID, {})" will be faster than @ref remove_last(). * * See also @ref remove_last. - * + * * @param cellID @ref IDIdx index of the cell to remove * @param columnsToSwap Vector of @ref IDIdx indices of the cells coming after @p cellID in the filtration. */ @@ -345,14 +371,14 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Returns the current number of columns in the matrix. - * + * * @return The number of columns. */ Index get_number_of_columns() const; /** * @brief Returns the dimension of the given column. - * + * * @param columnIndex @ref MatIdx index of the column representing the cell. * @return Dimension of the cell. */ @@ -364,7 +390,7 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param sourceColumnIndex @ref MatIdx index of the source column. * @param targetColumnIndex @ref MatIdx index of the target column. */ @@ -376,14 +402,12 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param sourceColumnIndex @ref MatIdx index of the source column. * @param coefficient Value to multiply. * @param targetColumnIndex @ref MatIdx index of the target column. */ - void multiply_target_and_add_to(Index sourceColumnIndex, - const Field_element& coefficient, - Index targetColumnIndex); + void multiply_target_and_add_to(Index sourceColumnIndex, const Field_element& coefficient, Index targetColumnIndex); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. @@ -391,18 +415,16 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * @warning They will be no verification to ensure that the addition makes sense for the validity of a * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation * of the barcode if done blindly. So should be used with care. - * + * * @param coefficient Value to multiply. * @param sourceColumnIndex @ref MatIdx index of the source column. * @param targetColumnIndex @ref MatIdx index of the target column. */ - void multiply_source_and_add_to(const Field_element& coefficient, - Index sourceColumnIndex, - Index targetColumnIndex); + void multiply_source_and_add_to(const Field_element& coefficient, Index sourceColumnIndex, Index targetColumnIndex); /** * @brief Indicates if the entry at given coordinates has value zero. - * + * * @param columnIndex @ref MatIdx index of the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -412,7 +434,7 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Indicates if the column at given index has value zero. Note that if the matrix is valid, this method * should always return false. - * + * * @param columnIndex @ref MatIdx index of the column. * @return true If the column has value zero. * @return false Otherwise. @@ -421,14 +443,14 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Returns the column with given @ref rowindex "row index" as pivot. Assumes that the pivot exists. - * + * * @param cellID @ref rowindex "Row index" of the pivot. * @return @ref MatIdx index of the column with the given pivot. */ Index get_column_with_pivot(ID_index cellID) const; /** * @brief Returns the @ref rowindex "row index" of the pivot of the given column. - * + * * @param columnIndex @ref MatIdx index of the column * @return The @ref rowindex "row index" of the pivot. */ @@ -436,14 +458,20 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, /** * @brief Resets the matrix to an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { + if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) Dim_opt::_reset(); + if constexpr (Master_matrix::Option_list::has_column_pairings) Pair_opt::_reset(); + if constexpr (Master_matrix::Option_list::can_retrieve_representative_cycles) Rep_opt::_reset(); + if constexpr (hasPivotToPosMap_) Pivot_to_pos_mapper_opt::map_.clear(); matrix_.clear(); pivotToColumnIndex_.clear(); nextIndex_ = 0; + nextPosition_ = 0; colSettings_ = colSettings; } @@ -451,26 +479,29 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, * @brief Assign operator. */ Chain_matrix& operator=(const Chain_matrix& other); + /** + * @brief Move assign operator. + */ + Chain_matrix& operator=(Chain_matrix&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(Chain_matrix& matrix1, Chain_matrix& matrix2) { - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); + friend void swap(Chain_matrix& matrix1, Chain_matrix& matrix2) noexcept + { + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); matrix1.matrix_.swap(matrix2.matrix_); matrix1.pivotToColumnIndex_.swap(matrix2.pivotToColumnIndex_); std::swap(matrix1.nextIndex_, matrix2.nextIndex_); + std::swap(matrix1.nextPosition_, matrix2.nextPosition_); std::swap(matrix1.colSettings_, matrix2.colSettings_); if constexpr (Master_matrix::Option_list::has_row_access) { - swap(static_cast(matrix1), - static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); } } @@ -479,29 +510,27 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, friend class Id_to_index_overlay, Master_matrix>; private: - using Dim_opt = typename Master_matrix::Matrix_dimension_option; - using Swap_opt = typename Master_matrix::Chain_vine_swap_option; - using Pair_opt = typename Master_matrix::Chain_pairing_option; - using Rep_opt = typename Master_matrix::Chain_representative_cycles_option; - using RA_opt = typename Master_matrix::Matrix_row_access_option; using Column_container = typename Master_matrix::Column_container; using Dictionary = typename Master_matrix::template Dictionary; using Barcode = typename Master_matrix::Barcode; using Bar_dictionary = typename Master_matrix::Bar_dictionary; - using Tmp_column = typename std::conditional, - std::map - >::type; + using Tmp_column = typename std:: + conditional, std::map>::type; + + friend Swap_opt; // direct access to index mapper + friend Pair_opt; // direct access to index mapper + friend Rep_opt; // direct access to index mapper Column_container matrix_; /**< Column container. */ Dictionary pivotToColumnIndex_; /**< Map from @ref IDIdx to @ref MatIdx index. */ Index nextIndex_; /**< Next unused column index. */ + Pos_index nextPosition_; /**< Next relative position in the filtration. */ Column_settings* colSettings_; /**< Entry factory. */ template std::vector _reduce_boundary(ID_index cellID, const Boundary_range& boundary, Dimension dim); - void _reduce_by_G(Tmp_column& column, std::vector& chainsInH, Index currentPivot); - void _reduce_by_F(Tmp_column& column, std::vector& chainsInF, Index currentPivot); + void _reduce_by_G(Tmp_column& column, std::vector& chainsInH, Index currentIndex); + void _reduce_by_F(Tmp_column& column, std::vector& chainsInF, Index currentIndex); void _build_from_H(ID_index cellID, Tmp_column& column, std::vector& chainsInH); void _update_largest_death_in_F(const std::vector& chainsInF); void _insert_chain(const Tmp_column& column, Dimension dimension); @@ -515,20 +544,18 @@ class Chain_matrix : public Master_matrix::Matrix_dimension_option, template void _container_insert(const Container& column, Index pos, Dimension dim); void _container_insert(const Column& column, [[maybe_unused]] Index pos = 0); - - constexpr Barcode& _barcode(); - constexpr Bar_dictionary& _indexToBar(); - constexpr Pos_index& _nextPosition(); }; template inline Chain_matrix::Chain_matrix(Column_settings* colSettings) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Pair_opt(), Swap_opt(), Rep_opt(), RA_opt(), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) {} @@ -536,19 +563,21 @@ template template inline Chain_matrix::Chain_matrix(const std::vector& orderedBoundaries, Column_settings* colSettings) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Pair_opt(), Swap_opt(), Rep_opt(), RA_opt(orderedBoundaries.size()), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) { matrix_.reserve(orderedBoundaries.size()); if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(orderedBoundaries.size()); } else { - pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + pivotToColumnIndex_.resize(orderedBoundaries.size(), Master_matrix::template get_null_value()); } for (const Boundary_range& b : orderedBoundaries) { @@ -557,21 +586,22 @@ inline Chain_matrix::Chain_matrix(const std::vector -inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, - Column_settings* colSettings) - : Dim_opt(-1), +inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings) + : Dim_opt(Master_matrix::template get_null_value()), Pair_opt(), Swap_opt(), Rep_opt(), RA_opt(numberOfColumns), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) { matrix_.reserve(numberOfColumns); if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(numberOfColumns); } else { - pivotToColumnIndex_.resize(numberOfColumns, -1); + pivotToColumnIndex_.resize(numberOfColumns, Master_matrix::template get_null_value()); } } @@ -580,12 +610,14 @@ template inline Chain_matrix::Chain_matrix(Column_settings* colSettings, const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Pair_opt(), Swap_opt(birthComparator, deathComparator), Rep_opt(), RA_opt(), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) {} @@ -595,19 +627,21 @@ inline Chain_matrix::Chain_matrix(const std::vector()), Pair_opt(), Swap_opt(birthComparator, deathComparator), Rep_opt(), RA_opt(orderedBoundaries.size()), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) { matrix_.reserve(orderedBoundaries.size()); if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(orderedBoundaries.size()); } else { - pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + pivotToColumnIndex_.resize(orderedBoundaries.size(), Master_matrix::template get_null_value()); } for (const Boundary_range& b : orderedBoundaries) { insert_boundary(b); @@ -616,23 +650,25 @@ inline Chain_matrix::Chain_matrix(const std::vector template -inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, +inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings, const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) - : Dim_opt(-1), + : Dim_opt(Master_matrix::template get_null_value()), Pair_opt(), Swap_opt(birthComparator, deathComparator), Rep_opt(), RA_opt(numberOfColumns), + Pivot_to_pos_mapper_opt(), nextIndex_(0), + nextPosition_(0), colSettings_(colSettings) { matrix_.reserve(numberOfColumns); if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(numberOfColumns); } else { - pivotToColumnIndex_.resize(numberOfColumns, -1); + pivotToColumnIndex_.resize(numberOfColumns, Master_matrix::template get_null_value()); } } @@ -643,13 +679,15 @@ inline Chain_matrix::Chain_matrix(const Chain_matrix& matrixToCop Swap_opt(static_cast(matrixToCopy)), Rep_opt(static_cast(matrixToCopy)), RA_opt(static_cast(matrixToCopy)), + Pivot_to_pos_mapper_opt(static_cast(matrixToCopy)), pivotToColumnIndex_(matrixToCopy.pivotToColumnIndex_), nextIndex_(matrixToCopy.nextIndex_), + nextPosition_(matrixToCopy.nextPosition_), colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) { matrix_.reserve(matrixToCopy.matrix_.size()); - for (const auto& cont : matrixToCopy.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : matrixToCopy.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -664,50 +702,56 @@ inline Chain_matrix::Chain_matrix(Chain_matrix&& other) noexcept Swap_opt(std::move(static_cast(other))), Rep_opt(std::move(static_cast(other))), RA_opt(std::move(static_cast(other))), + Pivot_to_pos_mapper_opt(std::move(static_cast(other))), matrix_(std::move(other.matrix_)), pivotToColumnIndex_(std::move(other.pivotToColumnIndex_)), nextIndex_(std::exchange(other.nextIndex_, 0)), - colSettings_(std::exchange(other.colSettings_, nullptr)) + nextPosition_(std::exchange(other.nextPosition_, 0)), + colSettings_(std::exchange(other.colSettings_, nullptr)) {} template template inline std::vector Chain_matrix::insert_boundary( - const Boundary_range& boundary, Dimension dim) + const Boundary_range& boundary, + Dimension dim) { return insert_boundary(nextIndex_, boundary, dim); } template template -inline std::vector Chain_matrix::insert_boundary( - ID_index cellID, const Boundary_range& boundary, Dimension dim) +inline std::vector +Chain_matrix::insert_boundary(ID_index cellID, const Boundary_range& boundary, Dimension dim) { if constexpr (!Master_matrix::Option_list::has_map_column_container) { if (pivotToColumnIndex_.size() <= cellID) { - pivotToColumnIndex_.resize(cellID * 2 + 1, -1); + pivotToColumnIndex_.resize((cellID * 2) + 1, Master_matrix::template get_null_value()); } } - if constexpr (Master_matrix::Option_list::has_vine_update && Master_matrix::Option_list::has_column_pairings) { + if constexpr (hasPivotToPosMap_) { if constexpr (Master_matrix::Option_list::has_map_column_container) { - Swap_opt::CP::pivotToPosition_.try_emplace(cellID, _nextPosition()); + Pivot_to_pos_mapper_opt::map_.try_emplace(cellID, nextPosition_); } else { - if (Swap_opt::CP::pivotToPosition_.size() <= cellID) - Swap_opt::CP::pivotToPosition_.resize(pivotToColumnIndex_.size(), -1); - Swap_opt::CP::pivotToPosition_[cellID] = _nextPosition(); + if (Pivot_to_pos_mapper_opt::map_.size() <= cellID) + Pivot_to_pos_mapper_opt::map_.resize(pivotToColumnIndex_.size(), + Master_matrix::template get_null_value()); + Pivot_to_pos_mapper_opt::map_[cellID] = nextPosition_; } } if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { - Dim_opt::update_up(dim == static_cast(-1) ? (boundary.size() == 0 ? 0 : boundary.size() - 1) : dim); + Dim_opt::_update_up(dim == Master_matrix::template get_null_value() + ? (boundary.size() == 0 ? 0 : boundary.size() - 1) + : dim); } return _reduce_boundary(cellID, boundary, dim); } template -inline typename Chain_matrix::Column& Chain_matrix::get_column(Index columnIndex) +inline typename Chain_matrix::Column& Chain_matrix::get_column(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_map_column_container) { return matrix_.at(columnIndex); @@ -732,20 +776,19 @@ inline void Chain_matrix::remove_maximal_cell(ID_index cellID) { static_assert(Master_matrix::Option_list::has_removable_columns, "'remove_maximal_cell' is not implemented for the chosen options."); - static_assert(Master_matrix::Option_list::has_map_column_container && - Master_matrix::Option_list::has_vine_update && + static_assert(Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_vine_update && Master_matrix::Option_list::has_column_pairings, "'remove_maximal_cell' is not implemented for the chosen options."); // TODO: find simple test to verify that col at columnIndex is maximal even without row access. - const auto& pivotToPosition = Swap_opt::CP::pivotToPosition_; + const auto& pivotToPosition = Pivot_to_pos_mapper_opt::map_; auto it = pivotToPosition.find(cellID); if (it == pivotToPosition.end()) return; // cell does not exists. TODO: put an assert instead? Pos_index startPos = it->second; Index startIndex = pivotToColumnIndex_.at(cellID); - if (startPos != _nextPosition() - 1) { + if (startPos != nextPosition_ - 1) { std::vector colToSwap; colToSwap.reserve(matrix_.size()); @@ -825,7 +868,7 @@ inline typename Chain_matrix::Index Chain_matrix:: template inline typename Chain_matrix::Dimension Chain_matrix::get_column_dimension( - Index columnIndex) const + Index columnIndex) const { return get_column(columnIndex).get_dimension(); } @@ -856,13 +899,13 @@ inline void Chain_matrix::multiply_source_and_add_to(const Field_ } template -inline bool Chain_matrix::is_zero_entry(Index columnIndex, ID_index rowIndex) const +inline bool Chain_matrix::is_zero_entry(Index columnIndex, ID_index rowIndex) const { return !get_column(columnIndex).is_non_zero(rowIndex); } template -inline bool Chain_matrix::is_zero_column(Index columnIndex) +inline bool Chain_matrix::is_zero_column(Index columnIndex) { return get_column(columnIndex).is_empty(); } @@ -887,18 +930,22 @@ inline typename Chain_matrix::ID_index Chain_matrix inline Chain_matrix& Chain_matrix::operator=(const Chain_matrix& other) { + if (this == &other) return *this; + Dim_opt::operator=(other); Swap_opt::operator=(other); Pair_opt::operator=(other); Rep_opt::operator=(other); + Pivot_to_pos_mapper_opt::operator=(other); matrix_.clear(); pivotToColumnIndex_ = other.pivotToColumnIndex_; nextIndex_ = other.nextIndex_; + nextPosition_ = other.nextPosition_; colSettings_ = other.colSettings_; matrix_.reserve(other.matrix_.size()); - for (const auto& cont : other.matrix_){ - if constexpr (Master_matrix::Option_list::has_map_column_container){ + for (const auto& cont : other.matrix_) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { _container_insert(cont.second, cont.first); } else { _container_insert(cont); @@ -908,29 +955,51 @@ inline Chain_matrix& Chain_matrix::operator=(const return *this; } +template +inline Chain_matrix& Chain_matrix::operator=(Chain_matrix&& other) noexcept +{ + Dim_opt::operator=(std::move(other)); + Pair_opt::operator=(std::move(other)); + Swap_opt::operator=(std::move(other)); + Rep_opt::operator=(std::move(other)); + RA_opt::operator=(std::move(other)); + Pivot_to_pos_mapper_opt::operator=(std::move(other)); + matrix_ = std::move(other.matrix_); + pivotToColumnIndex_ = std::move(other.pivotToColumnIndex_); + nextIndex_ = std::exchange(other.nextIndex_, 0); + nextPosition_ = std::exchange(other.nextPosition_, 0); + colSettings_ = std::exchange(other.colSettings_, nullptr); + + return *this; +} + template inline void Chain_matrix::print() const { std::cout << "Column Matrix:\n"; if constexpr (!Master_matrix::Option_list::has_map_column_container) { - for (ID_index i = 0; i < pivotToColumnIndex_.size() && pivotToColumnIndex_[i] != static_cast(-1); ++i) { + for (ID_index i = 0; i < pivotToColumnIndex_.size(); ++i) { Index pos = pivotToColumnIndex_[i]; - const Column& col = matrix_[pos]; - for (const auto& entry : col) { - std::cout << entry.get_row_index() << " "; + if (pos != Master_matrix::template get_null_value()) { + const Column& col = matrix_[pos]; + for (const auto& entry : col) { + std::cout << entry.get_row_index() << " "; + } + std::cout << "(" << i << ", " << pos << ")\n"; } - std::cout << "(" << i << ", " << pos << ")\n"; } if constexpr (Master_matrix::Option_list::has_row_access) { std::cout << "\n"; std::cout << "Row Matrix:\n"; - for (ID_index i = 0; i < pivotToColumnIndex_.size() && pivotToColumnIndex_[i] != static_cast(-1); ++i) { + for (ID_index i = 0; i < pivotToColumnIndex_.size(); ++i) { Index pos = pivotToColumnIndex_[i]; - const Row& row = RA_opt::get_row(pos); - for (const auto& entry : row) { - std::cout << entry.get_column_index() << " "; + if (pos != Master_matrix::template get_null_value()) { + const Row& row = RA_opt::get_row(pos); + for (const auto& entry : row) { + std::cout << entry.get_column_index() << " "; + } + std::cout << "(" << i << ", " << pos << ")\n"; } - std::cout << "(" << i << ", " << pos << ")\n"; } } } else { @@ -958,11 +1027,12 @@ inline void Chain_matrix::print() const template template -inline std::vector Chain_matrix::_reduce_boundary( - ID_index cellID, const Boundary_range& boundary, Dimension dim) +inline std::vector +Chain_matrix::_reduce_boundary(ID_index cellID, const Boundary_range& boundary, Dimension dim) { Tmp_column column(boundary.begin(), boundary.end()); - if (dim == static_cast(-1)) dim = boundary.begin() == boundary.end() ? 0 : boundary.size() - 1; + if (dim == Master_matrix::template get_null_value()) + dim = boundary.begin() == boundary.end() ? 0 : boundary.size() - 1; std::vector chainsInH; // for corresponding indices in H (paired columns) std::vector chainsInF; // for corresponding indices in F (unpaired, essential columns) @@ -1030,7 +1100,7 @@ inline void Chain_matrix::_reduce_by_G(Tmp_column& column, { Column& col = get_column(currentIndex); if constexpr (Master_matrix::Option_list::is_z2) { - _add_to(col, column, 1u); // Reduce with the column col_g + _add_to(col, column, 1U); // Reduce with the column col_g chainsInH.push_back(col.get_paired_chain_index()); // keep the col_h with which col_g is paired } else { Field_element coef = col.get_pivot_value(); @@ -1038,19 +1108,19 @@ inline void Chain_matrix::_reduce_by_G(Tmp_column& column, coef = operators.get_inverse(coef); operators.multiply_inplace(coef, operators.get_characteristic() - column.rbegin()->second); - _add_to(col, column, coef); // Reduce with the column col_g - chainsInH.emplace_back(col.get_paired_chain_index(), coef); // keep the col_h with which col_g is paired + _add_to(col, column, coef); // Reduce with the column col_g + chainsInH.emplace_back(col.get_paired_chain_index(), coef); // keep the col_h with which col_g is paired } } template -inline void Chain_matrix::_reduce_by_F(Tmp_column& column, +inline void Chain_matrix::_reduce_by_F(Tmp_column& column, std::vector& chainsInF, - Index currentIndex) + Index currentIndex) { Column& col = get_column(currentIndex); if constexpr (Master_matrix::Option_list::is_z2) { - _add_to(col, column, 1u); // Reduce with the column col_g + _add_to(col, column, 1U); // Reduce with the column col_g chainsInF.push_back(currentIndex); } else { Field_element coef = col.get_pivot_value(); @@ -1071,7 +1141,7 @@ inline void Chain_matrix::_build_from_H(ID_index cellID, if constexpr (Master_matrix::Option_list::is_z2) { column.insert(cellID); for (Index idx_h : chainsInH) { - _add_to(get_column(idx_h), column, 1u); + _add_to(get_column(idx_h), column, 1U); } } else { column.emplace(cellID, 1); @@ -1082,7 +1152,7 @@ inline void Chain_matrix::_build_from_H(ID_index cellID, } template -inline void Chain_matrix::_update_largest_death_in_F(const std::vector& chainsInF) +inline void Chain_matrix::_update_largest_death_in_F(const std::vector& chainsInF) { if constexpr (Master_matrix::Option_list::is_z2) { Index toUpdate = chainsInF[0]; @@ -1099,7 +1169,7 @@ inline void Chain_matrix::_update_largest_death_in_F(const std::v } template -inline void Chain_matrix::_insert_chain(const Tmp_column& column, Dimension dimension) +inline void Chain_matrix::_insert_chain(const Tmp_column& column, Dimension dimension) { _container_insert(column, nextIndex_, dimension); _add_bar(dimension); @@ -1121,7 +1191,7 @@ inline void Chain_matrix::_insert_chain(const Tmp_column& column, pairCol.assign_paired_chain(nextIndex_); if constexpr (Master_matrix::Option_list::has_column_pairings && Master_matrix::Option_list::has_vine_update) { - pairPos = Swap_opt::CP::pivotToPosition_[pairCol.get_pivot()]; + pairPos = Pivot_to_pos_mapper_opt::map_[pairCol.get_pivot()]; } _update_barcode(pairPos); @@ -1132,7 +1202,7 @@ inline void Chain_matrix::_insert_chain(const Tmp_column& column, template inline void Chain_matrix::_add_to(const Column& column, Tmp_column& set, - [[maybe_unused]] unsigned int coef) + [[maybe_unused]] unsigned int coef) { if constexpr (Master_matrix::Option_list::is_z2) { std::pair::iterator, bool> res_insert; @@ -1146,7 +1216,7 @@ inline void Chain_matrix::_add_to(const Column& column, auto& operators = colSettings_->operators; for (const Entry& entry : column) { auto res = set.emplace(entry.get_row_index(), entry.get_element()); - if (res.second){ + if (res.second) { operators.multiply_inplace(res.first->second, coef); } else { operators.multiply_and_add_inplace_back(entry.get_element(), coef, res.first->second); @@ -1163,7 +1233,7 @@ template inline void Chain_matrix::_add_to(Column& target, F&& addition) { auto pivot = target.get_pivot(); - addition(); + std::forward(addition)(); if (pivot != target.get_pivot()) { if constexpr (Master_matrix::Option_list::has_map_column_container) { @@ -1190,7 +1260,7 @@ inline void Chain_matrix::_remove_last(Index lastIndex) pivot = colToErase.get_pivot(); if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { - Dim_opt::update_down(colToErase.get_dimension()); + Dim_opt::_update_down(colToErase.get_dimension()); } if (colToErase.is_paired()) matrix_.at(colToErase.get_paired_chain_index()).unassign_paired_chain(); @@ -1204,11 +1274,11 @@ inline void Chain_matrix::_remove_last(Index lastIndex) pivot = colToErase.get_pivot(); if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { - Dim_opt::update_down(colToErase.get_dimension()); + Dim_opt::_update_down(colToErase.get_dimension()); } if (colToErase.is_paired()) matrix_.at(colToErase.get_paired_chain_index()).unassign_paired_chain(); - pivotToColumnIndex_[pivot] = -1; + pivotToColumnIndex_[pivot] = Master_matrix::template get_null_value(); matrix_.pop_back(); // TODO: resize matrix_ when a lot is removed? Could be not the best strategy if user inserts a lot back afterwards. } @@ -1217,17 +1287,12 @@ inline void Chain_matrix::_remove_last(Index lastIndex) --nextIndex_; // should not be updated when there are vine updates, as possibly lastIndex != nextIndex - 1 } + --nextPosition_; if constexpr (Master_matrix::Option_list::has_column_pairings) { - auto it = _indexToBar().find(--_nextPosition()); - typename Barcode::iterator bar = it->second; - - if (bar->death == static_cast(-1)) - _barcode().erase(bar); - else - bar->death = -1; - - _indexToBar().erase(it); - if constexpr (Master_matrix::Option_list::has_vine_update) Swap_opt::CP::pivotToPosition_.erase(pivot); + Pair_opt::_erase_bar(nextPosition_); + } + if constexpr (hasPivotToPosMap_) { + Pivot_to_pos_mapper_opt::map_.erase(pivot); } if constexpr (Master_matrix::Option_list::has_row_access) { @@ -1245,30 +1310,18 @@ template inline void Chain_matrix::_update_barcode(Pos_index birth) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_removable_columns) { - auto& barIt = _indexToBar().at(birth); - barIt->death = _nextPosition(); - _indexToBar().try_emplace(_nextPosition(), barIt); // list so iterators are stable - } else { - _barcode()[_indexToBar()[birth]].death = _nextPosition(); - _indexToBar().push_back(_indexToBar()[birth]); - } - ++_nextPosition(); + Pair_opt::_update_barcode(birth, nextPosition_); } + ++nextPosition_; } template inline void Chain_matrix::_add_bar(Dimension dim) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - _barcode().emplace_back(_nextPosition(), -1, dim); - if constexpr (Master_matrix::Option_list::has_removable_columns) { - _indexToBar().try_emplace(_nextPosition(), --_barcode().end()); - } else { - _indexToBar().push_back(_barcode().size() - 1); - } - ++_nextPosition(); + Pair_opt::_add_bar(dim, nextPosition_); } + ++nextPosition_; } template @@ -1284,13 +1337,13 @@ inline void Chain_matrix::_container_insert(const Container& colu if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.try_emplace(pivot, pos); if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, dim, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(pos, column, dim, RA_opt::rows_, colSettings_); + matrix_.emplace_back(pos, column, dim, RA_opt::_get_rows_ptr(), colSettings_); } else { matrix_.emplace_back(column, dim, colSettings_); } @@ -1303,47 +1356,19 @@ inline void Chain_matrix::_container_insert(const Column& column, { if constexpr (Master_matrix::Option_list::has_map_column_container) { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::rows_, colSettings_)); + matrix_.try_emplace(pos, Column(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_)); } else { matrix_.try_emplace(pos, Column(column, colSettings_)); } } else { if constexpr (Master_matrix::Option_list::has_row_access) { - matrix_.emplace_back(column, column.get_column_index(), RA_opt::rows_, colSettings_); + matrix_.emplace_back(column, column.get_column_index(), RA_opt::_get_rows_ptr(), colSettings_); } else { matrix_.emplace_back(column, colSettings_); } } } -template -inline constexpr typename Chain_matrix::Barcode& Chain_matrix::_barcode() -{ - if constexpr (Master_matrix::Option_list::has_vine_update) - return Swap_opt::template Chain_pairing::barcode_; - else - return Pair_opt::barcode_; -} - -template -inline constexpr typename Chain_matrix::Bar_dictionary& -Chain_matrix::_indexToBar() -{ - if constexpr (Master_matrix::Option_list::has_vine_update) - return Swap_opt::template Chain_pairing::indexToBar_; - else - return Pair_opt::indexToBar_; -} - -template -inline constexpr typename Chain_matrix::Pos_index& Chain_matrix::_nextPosition() -{ - if constexpr (Master_matrix::Option_list::has_vine_update) - return Swap_opt::template Chain_pairing::nextPosition_; - else - return Pair_opt::nextPosition_; -} - } // namespace persistence_matrix } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h b/multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h index be885d18..8ce0309d 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Id_to_index_overlay.h @@ -33,39 +33,39 @@ namespace persistence_matrix { * * @brief Overlay for @ref mp_matrices "non-basic matrices" replacing all input and output @ref MatIdx indices of * the original methods with @ref IDIdx indices. - * + * * @tparam Underlying_matrix %Matrix type taking the overlay. * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Id_to_index_overlay +class Id_to_index_overlay { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Row = typename Master_matrix::Row; /**< Row type, - only necessary with row access option. */ - using Bar = typename Master_matrix::Bar; /**< Bar type. */ - using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ - using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to - necessary external classes. */ + using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Row = typename Master_matrix::Row; /**< Row type, + only necessary with row access option. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ + using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ /** * @brief Constructs an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Id_to_index_overlay(Column_settings* colSettings); /** @@ -73,31 +73,30 @@ class Id_to_index_overlay * to a column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by * increasing IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting * with 0. - * + * * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the @ref Id_to_index_overlay(unsigned int, Column_settings*) * constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Id_to_index_overlay(const std::vector& orderedBoundaries, - Column_settings* colSettings); + Id_to_index_overlay(const std::vector& orderedBoundaries, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Id_to_index_overlay(unsigned int numberOfColumns, Column_settings* colSettings); /** @@ -108,11 +107,11 @@ class Id_to_index_overlay * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -124,35 +123,35 @@ class Id_to_index_overlay */ template Id_to_index_overlay(Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** - * @brief Only available for @ref chainmatrix "chain matrices". + * @brief Only available for @ref chainmatrix "chain matrices". * Constructs a new matrix from the given ranges of @ref Matrix::Entry_representative. Each range corresponds to a * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing - * IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting with 0. + * IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting with 0. * * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the @ref Id_to_index_overlay(unsigned int, Column_settings*, * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -163,9 +162,9 @@ class Id_to_index_overlay * columns. */ template - Id_to_index_overlay(const std::vector& orderedBoundaries, - Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + Id_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** * @brief Only available for @ref chainmatrix "chain matrices". @@ -175,12 +174,12 @@ class Id_to_index_overlay * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -191,24 +190,23 @@ class Id_to_index_overlay * columns. */ template - Id_to_index_overlay(unsigned int numberOfColumns, + Id_to_index_overlay(unsigned int numberOfColumns, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** * @brief Copy constructor. If @p operators or @p entryConstructor is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ - Id_to_index_overlay(const Id_to_index_overlay& matrixToCopy, - Column_settings* colSettings = nullptr); + Id_to_index_overlay(const Id_to_index_overlay& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Id_to_index_overlay(Id_to_index_overlay&& other) noexcept; @@ -218,9 +216,9 @@ class Id_to_index_overlay ~Id_to_index_overlay(); /** - * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. - * This means that it is assumed that this method is called on boundaries in the order of the filtration. - * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, * starting at 0. If it is not the case, use the other * @ref insert_boundary(ID_index, const Boundary_range&, Dimension) "insert_boundary" instead by indicating the * cell ID used in the boundaries when the cell is inserted. @@ -229,23 +227,24 @@ class Id_to_index_overlay * a more general entry complex. This includes cubical complexes or Morse complexes for example. * * The content of the new column will vary depending on the underlying @ref mp_matrices "type of the matrix": - * - If it is a boundary type matrix and only \f$ R \f$ is stored, the boundary is just copied. The column will only + * - If it is a boundary type matrix and only \f$ R \f$ is stored, the boundary is just copied. The column will only * be reduced later when the barcode is requested in order to apply some optimizations with the additional * knowledge. Hence, the barcode will also not be updated. * - If it is a boundary type matrix and both \f$ R \f$ and \f$ U \f$ are stored, the new boundary is stored in its * reduced form and the barcode, if active, is also updated. - * - If it is a chain type matrix, the new column is of the form - * `IDIdx + linear combination of older column IDIdxs`, where the combination is deduced while reducing the + * - If it is a chain type matrix, the new column is of the form + * `IDIdx + linear combination of older column IDIdxs`, where the combination is deduced while reducing the * given boundary. If the barcode is stored, it will also be updated. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Boundary generating the new column. The content should be ordered by ID. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief It does the same as the other version, but allows the boundary cells to be identified without restrictions * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then @@ -253,23 +252,25 @@ class Id_to_index_overlay * * As a cell has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that * the cells are inserted by order of filtration), it is sufficient to indicate the ID of the cell being inserted. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param cellIndex @ref IDIdx index to use to identify the new cell. - * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the - * @p cellIndex values of precedent calls of the method for the corresponding cells and should be ordered in + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p cellIndex values of precedent calls of the method for the corresponding cells and should be ordered in * increasing order. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(ID_index cellIndex, + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** - * @brief Returns the column at the given @ref IDIdx index. + * @brief Returns the column at the given @ref IDIdx index. * For @ref boundarymatrix "RU matrices", the returned column is from \f$ R \f$. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param cellID @ref IDIdx index of the column to return. * @return Reference to the column. */ @@ -278,12 +279,12 @@ class Id_to_index_overlay * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. * Returns the row at the given @ref rowindex "row index". * For @ref boundarymatrix "RU matrices", the returned row is from \f$ R \f$. - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. * * @warning The @ref Entry_column_index::get_column_index "get_column_index" method of the row entries returns the * original @ref PosIdx indices (before any swaps) for @ref boundarymatrix "boundary matrices" and * @ref MatIdx indices for @ref chainmatrix "chain matrices". - * + * * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occurred. * @return Reference to the row. @@ -296,21 +297,21 @@ class Id_to_index_overlay * @ref PersistenceMatrixOptions::has_column_and_row_swaps are true: * cleans up maps used for the lazy row swaps. * - @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows are true: - * assumes that the row is empty and removes it. + * assumes that the row is empty and removes it. * - Otherwise, does nothing. * - @ref boundarymatrix "boundary matrix" with \f$ U \f$ stored: only \f$ R \f$ is affected by the above. * If properly used, \f$ U \f$ will never have empty rows. * - @ref chainmatrix "chain matrix": only available if @ref PersistenceMatrixOptions::has_row_access and * @ref PersistenceMatrixOptions::has_removable_rows are true. - * Assumes that the row is empty and removes it. + * Assumes that the row is empty and removes it. * * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row entries are not - * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when * the column entries are destroyed later. The row access is just meant as a "read only" access to the rows and the * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from * dictionaries. This allows to avoid testing the emptiness of a row at each column entry removal, what can be - * quite frequent. - * + * quite frequent. + * * @param rowIndex @ref rowindex "Row index" of the empty row to remove. */ void erase_empty_row(ID_index rowIndex); @@ -330,7 +331,7 @@ class Id_to_index_overlay * do not need to be true. * * See also @ref remove_last. - * + * * @param cellID @ref IDIdx index of the cell to remove. */ void remove_maximal_cell(ID_index cellID); @@ -343,7 +344,7 @@ class Id_to_index_overlay * The maximality of the cell is not verified. * Also updates the barcode if it was computed. * - * To maintain the compatibility, vine swaps are done to move the cell up to the end of the filtration. Once at + * To maintain the compatibility, vine swaps are done to move the cell up to the end of the filtration. Once at * the end, the removal is trivial. But for @ref chainmatrix "chain matrices", swaps do not actually swap the position * of the column every time, so the cells appearing after @p cellIndex in the filtration have to be searched first * within the matrix. If the user has an easy access to the @ref IDIdx of the cells in the order of filtration, @@ -353,7 +354,7 @@ class Id_to_index_overlay * will be faster than @ref remove_last(). * * See also @ref remove_last. - * + * * @param cellID @ref IDIdx index of the cell to remove. * @param columnsToSwap Vector of @ref IDIdx indices of the cells coming after @p cellID in the filtration. */ @@ -363,7 +364,7 @@ class Id_to_index_overlay * matrix is a @ref chainmatrix "chain matrix", either @ref PersistenceMatrixOptions::has_map_column_container has to * be true or @ref PersistenceMatrixOptions::has_vine_update has to be false. * Removes the last cell in the filtration from the matrix and updates the barcode if it is stored. - * + * * See also @ref remove_maximal_cell. * * For @ref chainmatrix "chain matrices", if @ref PersistenceMatrixOptions::has_vine_update is true, the last cell @@ -375,21 +376,21 @@ class Id_to_index_overlay void remove_last(); /** - * @brief Returns the maximal dimension of a cell stored in the matrix. Only available + * @brief Returns the maximal dimension of a cell stored in the matrix. Only available * if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access is true. - * + * * @return The maximal dimension. */ Dimension get_max_dimension() const; /** * @brief Returns the current number of columns in the matrix. - * + * * @return The number of columns. */ Index get_number_of_columns() const; /** * @brief Returns the dimension of the given cell. Only available for @ref mp_matrices "non-basic matrices". - * + * * @param cellID @ref IDIdx index of the cell. * @return Dimension of the cell. */ @@ -401,7 +402,7 @@ class Id_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param sourceCellID @ref IDIdx index of the source column. * @param targetCellID @ref IDIdx index of the target column. */ @@ -413,7 +414,7 @@ class Id_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param sourceCellID @ref IDIdx index of the source column. * @param coefficient Value to multiply. * @param targetCellID @ref IDIdx index of the target column. @@ -426,7 +427,7 @@ class Id_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param coefficient Value to multiply. * @param sourceCellID @ref IDIdx index of the source column. * @param targetCellID @ref IDIdx index of the target column. @@ -435,22 +436,22 @@ class Id_to_index_overlay /** * @brief Zeroes the entry at the given coordinates. Not available for @ref chainmatrix "chain matrices". - * In general, should be used with care to not destroy the validity + * In general, should be used with care to not destroy the validity * of the persistence related properties of the matrix. * * For @ref boundarymatrix "RU matrices", zeros only the entry in \f$ R \f$. - * + * * @param cellID @ref IDIdx index of the cell corresponding to the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. */ void zero_entry(ID_index cellID, ID_index rowIndex); /** * @brief Zeroes the column at the given index. Not available for @ref chainmatrix "chain matrices". - * In general, should be used with care to not destroy the validity + * In general, should be used with care to not destroy the validity * of the persistence related properties of the matrix. * * For @ref boundarymatrix "RU matrices", zeros only the column in \f$ R \f$. - * + * * @param cellID @ref IDIdx index of the cell corresponding to the column. */ void zero_column(ID_index cellID); @@ -458,7 +459,7 @@ class Id_to_index_overlay * @brief Indicates if the entry at given coordinates has value zero. * * For @ref boundarymatrix "RU matrices", looks into \f$ R \f$. - * + * * @param cellID @ref IDIdx index of the cell corresponding to the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -472,7 +473,7 @@ class Id_to_index_overlay * * Note that for @ref chainmatrix "chain matrices", this method should always return false, as a valid * @ref chainmatrix "chain matrix" never has empty columns. - * + * * @param cellID @ref IDIdx index of the cell corresponding to the column. * @return true If the column has value zero. * @return false Otherwise. @@ -486,14 +487,14 @@ class Id_to_index_overlay * Recall that the row indices for @ref chainmatrix "chain matrices" correspond to the @ref IDIdx indices and that * the row indices for a @ref boundarymatrix "RU matrix" correspond to the updated @ref IDIdx indices which got * potentially swapped by a vine swap. - * + * * @param cellIndex @ref rowindex "Row index" of the pivot. * @return @ref IDIdx index of the column with the given pivot. */ ID_index get_column_with_pivot(ID_index cellIndex) const; /** * @brief Returns the @ref rowindex "row index" of the pivot of the given column. - * + * * @param cellID @ref IDIdx index of the cell corresponding to the column. * @return The @ref rowindex "row index" of the pivot. */ @@ -501,27 +502,34 @@ class Id_to_index_overlay /** * @brief Resets the matrix to an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { matrix_.reset(colSettings); + if constexpr (Master_matrix::Option_list::is_of_boundary_type) + if (idToIndex_ != nullptr) idToIndex_->clear(); nextIndex_ = 0; } - // void set_operators(Field_operators* operators) { matrix_.set_operators(operators); } - /** * @brief Assign operator. */ Id_to_index_overlay& operator=(const Id_to_index_overlay& other); + /** + * @brief Assign operator. + */ + Id_to_index_overlay& operator=(Id_to_index_overlay&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(Id_to_index_overlay& matrix1, Id_to_index_overlay& matrix2) { + friend void swap(Id_to_index_overlay& matrix1, Id_to_index_overlay& matrix2) noexcept + { swap(matrix1.matrix_, matrix2.matrix_); - if (Master_matrix::Option_list::is_of_boundary_type) std::swap(matrix1.idToIndex_, matrix2.idToIndex_); + std::swap(matrix1.idToIndex_, matrix2.idToIndex_); std::swap(matrix1.nextIndex_, matrix2.nextIndex_); } @@ -537,18 +545,18 @@ class Id_to_index_overlay * * @warning For simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$), we assume that * @ref get_current_barcode is only called once, when the matrix is completed. - * - * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three attributes: * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. */ const Barcode& get_current_barcode(); - + /** * @brief Only available for simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$) and if * @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. * Swaps the two given columns. Note that it really just swaps two columns and do not updates * anything else, nor performs additions to maintain some properties on the matrix. - * + * * @param cellID1 First column @ref IDIdx index to swap. * @param cellID2 Second column @ref IDIdx index to swap. */ @@ -558,7 +566,7 @@ class Id_to_index_overlay * and if @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. * Swaps the two given rows. Note that it really just swaps two rows and do not updates * anything else, nor performs additions to maintain some properties on the matrix. - * + * * @param rowIndex1 First @ref rowindex "row index" to swap. * @param rowIndex2 Second @ref rowindex "row index" to swap. */ @@ -567,7 +575,7 @@ class Id_to_index_overlay * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. * Does the same than @ref vine_swap, but assumes that the swap is non trivial and * therefore skips a part of the case study. - * + * * @param cellID1 @ref IDIdx index of the first cell. * @param cellID2 @ref IDIdx index of the second cell. It is assumed that the @ref PosIdx of both only differs by one. * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of @@ -583,7 +591,7 @@ class Id_to_index_overlay * at swapped positions. Of course, the two cells should not have a face/coface relation which each other ; * \f$ F' \f$ has to be a valid filtration. * See @cite vineyards for more information about vine and vineyards. - * + * * @param cellID1 @ref IDIdx index of the first cell. * @param cellID2 @ref IDIdx index of the second cell. It is assumed that the @ref PosIdx of both only differs by one. * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of @@ -591,7 +599,7 @@ class Id_to_index_overlay * \f$ max(pos1, pos2) \f$. */ ID_index vine_swap(ID_index cellID1, ID_index cellID2); - + /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Pre-computes * the representative cycles of the current state of the filtration represented by the matrix. @@ -603,14 +611,14 @@ class Id_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. * Returns all representative cycles of the current filtration. - * + * * @return A const reference to the vector of representative cycles. */ const std::vector& get_representative_cycles(); /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. * Returns the cycle representing the given bar. - * + * * @param bar A bar from the current barcode. * @return A const reference to the cycle representing @p bar. */ @@ -619,9 +627,9 @@ class Id_to_index_overlay private: using Dictionary = typename Master_matrix::template Dictionary; - Underlying_matrix matrix_; /**< Interfaced matrix. */ - Dictionary* idToIndex_; /**< Map from @ref IDIdx index to @ref MatIdx index. */ - Index nextIndex_; /**< Next unused index. */ + Underlying_matrix matrix_; /**< Interfaced matrix. */ + Dictionary* idToIndex_; /**< Map from @ref IDIdx index to @ref MatIdx index. */ + Index nextIndex_; /**< Next unused index. */ void _initialize_map(unsigned int size); Index _id_to_index(ID_index id) const; @@ -630,7 +638,7 @@ class Id_to_index_overlay template inline Id_to_index_overlay::Id_to_index_overlay(Column_settings* colSettings) - : matrix_(colSettings), idToIndex_(nullptr), nextIndex_(0) + : matrix_(colSettings), idToIndex_(nullptr), nextIndex_(0) { _initialize_map(0); } @@ -638,8 +646,9 @@ inline Id_to_index_overlay::Id_to_index_overla template template inline Id_to_index_overlay::Id_to_index_overlay( - const std::vector& orderedBoundaries, Column_settings* colSettings) - : matrix_(orderedBoundaries, colSettings), idToIndex_(nullptr), nextIndex_(orderedBoundaries.size()) + const std::vector& orderedBoundaries, + Column_settings* colSettings) + : matrix_(orderedBoundaries, colSettings), idToIndex_(nullptr), nextIndex_(orderedBoundaries.size()) { _initialize_map(orderedBoundaries.size()); if constexpr (Master_matrix::Option_list::is_of_boundary_type) { @@ -651,8 +660,8 @@ inline Id_to_index_overlay::Id_to_index_overla template inline Id_to_index_overlay::Id_to_index_overlay(unsigned int numberOfColumns, - Column_settings* colSettings) - : matrix_(numberOfColumns, colSettings), idToIndex_(nullptr), nextIndex_(0) + Column_settings* colSettings) + : matrix_(numberOfColumns, colSettings), idToIndex_(nullptr), nextIndex_(0) { _initialize_map(numberOfColumns); } @@ -660,10 +669,10 @@ inline Id_to_index_overlay::Id_to_index_overla template template inline Id_to_index_overlay::Id_to_index_overlay( - Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) - : matrix_(colSettings, birthComparator, deathComparator), idToIndex_(nullptr), nextIndex_(0) + : matrix_(colSettings, birthComparator, deathComparator), idToIndex_(nullptr), nextIndex_(0) { _initialize_map(0); } @@ -671,13 +680,13 @@ inline Id_to_index_overlay::Id_to_index_overla template template inline Id_to_index_overlay::Id_to_index_overlay( - const std::vector& orderedBoundaries, + const std::vector& orderedBoundaries, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) : matrix_(orderedBoundaries, colSettings, birthComparator, deathComparator), idToIndex_(nullptr), - nextIndex_(orderedBoundaries.size()) + nextIndex_(orderedBoundaries.size()) { _initialize_map(orderedBoundaries.size()); if constexpr (Master_matrix::Option_list::is_of_boundary_type) { @@ -690,23 +699,20 @@ inline Id_to_index_overlay::Id_to_index_overla template template inline Id_to_index_overlay::Id_to_index_overlay( - unsigned int numberOfColumns, + unsigned int numberOfColumns, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) - : matrix_(numberOfColumns, colSettings, birthComparator, deathComparator), - idToIndex_(nullptr), - nextIndex_(0) + : matrix_(numberOfColumns, colSettings, birthComparator, deathComparator), idToIndex_(nullptr), nextIndex_(0) { _initialize_map(numberOfColumns); } template inline Id_to_index_overlay::Id_to_index_overlay( - const Id_to_index_overlay& matrixToCopy, Column_settings* colSettings) - : matrix_(matrixToCopy.matrix_, colSettings), - idToIndex_(nullptr), - nextIndex_(matrixToCopy.nextIndex_) + const Id_to_index_overlay& matrixToCopy, + Column_settings* colSettings) + : matrix_(matrixToCopy.matrix_, colSettings), idToIndex_(nullptr), nextIndex_(matrixToCopy.nextIndex_) { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { idToIndex_ = new Dictionary(*matrixToCopy.idToIndex_); @@ -719,21 +725,22 @@ template inline Id_to_index_overlay::Id_to_index_overlay(Id_to_index_overlay&& other) noexcept : matrix_(std::move(other.matrix_)), idToIndex_(std::exchange(other.idToIndex_, nullptr)), - nextIndex_(std::exchange(other.nextIndex_, 0)) -{} + nextIndex_(std::exchange(other.nextIndex_, 0)) +{ +} template -inline Id_to_index_overlay::~Id_to_index_overlay() +inline Id_to_index_overlay::~Id_to_index_overlay() { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (idToIndex_ != nullptr) delete idToIndex_; + delete idToIndex_; } } template template inline void Id_to_index_overlay::insert_boundary(const Boundary_range& boundary, - Dimension dim) + Dimension dim) { matrix_.insert_boundary(boundary, dim); if constexpr (Master_matrix::Option_list::is_of_boundary_type) { @@ -753,15 +760,16 @@ inline void Id_to_index_overlay::insert_bounda template template inline void Id_to_index_overlay::insert_boundary(ID_index cellIndex, - const Boundary_range& boundary, - Dimension dim) + const Boundary_range& boundary, + Dimension dim) { if constexpr (Master_matrix::Option_list::has_map_column_container) { GUDHI_CHECK(idToIndex_->find(cellIndex) == idToIndex_->end(), std::invalid_argument("Id_to_index_overlay::insert_boundary - Index for simplex already chosen!")); } else { - GUDHI_CHECK((idToIndex_->size() <= cellIndex || _id_to_index(cellIndex) == static_cast(-1)), - std::invalid_argument("Id_to_index_overlay::insert_boundary - Index for simplex already chosen!")); + GUDHI_CHECK( + (idToIndex_->size() <= cellIndex || _id_to_index(cellIndex) == Master_matrix::template get_null_value()), + std::invalid_argument("Id_to_index_overlay::insert_boundary - Index for simplex already chosen!")); } matrix_.insert_boundary(cellIndex, boundary, dim); if constexpr (Master_matrix::Option_list::is_of_boundary_type) { @@ -769,7 +777,7 @@ inline void Id_to_index_overlay::insert_bounda idToIndex_->emplace(cellIndex, nextIndex_); } else { if (idToIndex_->size() <= cellIndex) { - idToIndex_->resize(cellIndex + 1, -1); + idToIndex_->resize(cellIndex + 1, Master_matrix::template get_null_value()); } _id_to_index(cellIndex) = nextIndex_; } @@ -779,26 +787,26 @@ inline void Id_to_index_overlay::insert_bounda template inline typename Id_to_index_overlay::Column& -Id_to_index_overlay::get_column(ID_index cellID) +Id_to_index_overlay::get_column(ID_index cellID) { return matrix_.get_column(_id_to_index(cellID)); } template inline typename Id_to_index_overlay::Row& -Id_to_index_overlay::get_row(ID_index rowIndex) +Id_to_index_overlay::get_row(ID_index rowIndex) { return matrix_.get_row(rowIndex); } template -inline void Id_to_index_overlay::erase_empty_row(ID_index rowIndex) +inline void Id_to_index_overlay::erase_empty_row(ID_index rowIndex) { return matrix_.erase_empty_row(rowIndex); } template -inline void Id_to_index_overlay::remove_maximal_cell(ID_index cellID) +inline void Id_to_index_overlay::remove_maximal_cell(ID_index cellID) { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { std::vector indexToID(nextIndex_); @@ -808,7 +816,7 @@ inline void Id_to_index_overlay::remove_maxima } } else { for (ID_index i = 0; i < idToIndex_->size(); ++i) { - if (_id_to_index(i) != static_cast(-1)) indexToID[_id_to_index(i)] = i; + if (_id_to_index(i) != Master_matrix::template get_null_value()) indexToID[_id_to_index(i)] = i; } } --nextIndex_; @@ -823,7 +831,7 @@ inline void Id_to_index_overlay::remove_maxima if constexpr (Master_matrix::Option_list::has_map_column_container) { idToIndex_->erase(cellID); } else { - _id_to_index(cellID) = -1; + _id_to_index(cellID) = Master_matrix::template get_null_value(); } } else { matrix_.remove_maximal_cell(cellID); @@ -832,20 +840,22 @@ inline void Id_to_index_overlay::remove_maxima template inline void Id_to_index_overlay::remove_maximal_cell( - ID_index cellID, const std::vector& columnsToSwap) + ID_index cellID, + const std::vector& columnsToSwap) { static_assert(!Master_matrix::Option_list::is_of_boundary_type, "'remove_maximal_cell(ID_index,const std::vector&)' is not available for the chosen options."); std::vector translatedIndices; - std::transform(columnsToSwap.cbegin(), columnsToSwap.cend(), std::back_inserter(translatedIndices), - [&](ID_index id) { return _id_to_index(id); }); + std::transform(columnsToSwap.cbegin(), columnsToSwap.cend(), std::back_inserter(translatedIndices), [&](ID_index id) { + return _id_to_index(id); + }); matrix_.remove_maximal_cell(cellID, translatedIndices); } template -inline void Id_to_index_overlay::remove_last() +inline void Id_to_index_overlay::remove_last() { - if (idToIndex_->empty()) return; //empty matrix + if (idToIndex_->empty()) return; // empty matrix matrix_.remove_last(); @@ -853,101 +863,106 @@ inline void Id_to_index_overlay::remove_last() --nextIndex_; if constexpr (Master_matrix::Option_list::has_map_column_container) { auto it = idToIndex_->begin(); - while (it->second != nextIndex_) ++it; //should never reach idToIndex_->end() + while (it->second != nextIndex_) ++it; // should never reach idToIndex_->end() idToIndex_->erase(it); } else { Index id = idToIndex_->size() - 1; - while (_id_to_index(id) == static_cast(-1)) --id; // should always stop before reaching -1 + // should always stop before reaching -1 + while (_id_to_index(id) == Master_matrix::template get_null_value()) --id; GUDHI_CHECK(_id_to_index(id) == nextIndex_, std::logic_error("Id_to_index_overlay::remove_last - Indexation problem.")); - _id_to_index(id) = -1; + _id_to_index(id) = Master_matrix::template get_null_value(); } } } template inline typename Id_to_index_overlay::Dimension -Id_to_index_overlay::get_max_dimension() const +Id_to_index_overlay::get_max_dimension() const { return matrix_.get_max_dimension(); } template inline typename Id_to_index_overlay::Index -Id_to_index_overlay::get_number_of_columns() const +Id_to_index_overlay::get_number_of_columns() const { return matrix_.get_number_of_columns(); } template inline typename Id_to_index_overlay::Dimension -Id_to_index_overlay::get_column_dimension(ID_index cellID) const +Id_to_index_overlay::get_column_dimension(ID_index cellID) const { return matrix_.get_column_dimension(_id_to_index(cellID)); } template -inline void Id_to_index_overlay::add_to(ID_index sourceCellID, ID_index targetCellID) +inline void Id_to_index_overlay::add_to(ID_index sourceCellID, ID_index targetCellID) { return matrix_.add_to(_id_to_index(sourceCellID), _id_to_index(targetCellID)); } template inline void Id_to_index_overlay::multiply_target_and_add_to( - ID_index sourceCellID, const Field_element& coefficient, ID_index targetCellID) + ID_index sourceCellID, + const Field_element& coefficient, + ID_index targetCellID) { return matrix_.multiply_target_and_add_to(_id_to_index(sourceCellID), coefficient, _id_to_index(targetCellID)); } template inline void Id_to_index_overlay::multiply_source_and_add_to( - const Field_element& coefficient, ID_index sourceCellID, ID_index targetCellID) + const Field_element& coefficient, + ID_index sourceCellID, + ID_index targetCellID) { return matrix_.multiply_source_and_add_to(coefficient, _id_to_index(sourceCellID), _id_to_index(targetCellID)); } template -inline void Id_to_index_overlay::zero_entry(ID_index cellID, ID_index rowIndex) +inline void Id_to_index_overlay::zero_entry(ID_index cellID, ID_index rowIndex) { return matrix_.zero_entry(_id_to_index(cellID), rowIndex); } template -inline void Id_to_index_overlay::zero_column(ID_index cellID) +inline void Id_to_index_overlay::zero_column(ID_index cellID) { return matrix_.zero_column(_id_to_index(cellID)); } template inline bool Id_to_index_overlay::is_zero_entry(ID_index cellID, - ID_index rowIndex) const + ID_index rowIndex) const { return matrix_.is_zero_entry(_id_to_index(cellID), rowIndex); } template -inline bool Id_to_index_overlay::is_zero_column(ID_index cellID) +inline bool Id_to_index_overlay::is_zero_column(ID_index cellID) { return matrix_.is_zero_column(_id_to_index(cellID)); } template inline typename Id_to_index_overlay::ID_index -Id_to_index_overlay::get_column_with_pivot(ID_index simplexIndex) const +Id_to_index_overlay::get_column_with_pivot(ID_index cellIndex) const { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - Index pos = matrix_.get_column_with_pivot(simplexIndex); + Index pos = matrix_.get_column_with_pivot(cellIndex); ID_index i = 0; while (_id_to_index(i) != pos) ++i; return i; } else { - return simplexIndex; + return cellIndex; } } template inline typename Id_to_index_overlay::ID_index -Id_to_index_overlay::get_pivot(ID_index cellID) +Id_to_index_overlay::get_pivot(ID_index cellID) { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { return matrix_.get_pivot(_id_to_index(cellID)); @@ -958,67 +973,85 @@ Id_to_index_overlay::get_pivot(ID_index cellID template inline Id_to_index_overlay& -Id_to_index_overlay::operator=(const Id_to_index_overlay& other) +Id_to_index_overlay::operator=(const Id_to_index_overlay& other) { + if (this == &other) return *this; + matrix_ = other.matrix_; - if (Master_matrix::Option_list::is_of_boundary_type) + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + delete idToIndex_; idToIndex_ = other.idToIndex_; - else + } else { idToIndex_ = &matrix_.pivotToColumnIndex_; + } nextIndex_ = other.nextIndex_; return *this; } template -inline void Id_to_index_overlay::print() +inline Id_to_index_overlay& +Id_to_index_overlay::operator=(Id_to_index_overlay&& other) noexcept +{ + matrix_ = std::move(other.matrix_); + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + delete idToIndex_; + } + idToIndex_ = std::exchange(other.idToIndex_, nullptr); + nextIndex_ = std::exchange(other.nextIndex_, 0); + + return *this; +} + +template +inline void Id_to_index_overlay::print() { return matrix_.print(); } template inline const typename Id_to_index_overlay::Barcode& -Id_to_index_overlay::get_current_barcode() +Id_to_index_overlay::get_current_barcode() { return matrix_.get_current_barcode(); } template -inline void Id_to_index_overlay::update_representative_cycles() +inline void Id_to_index_overlay::update_representative_cycles() { matrix_.update_representative_cycles(); } template inline const std::vector::Cycle>& -Id_to_index_overlay::get_representative_cycles() +Id_to_index_overlay::get_representative_cycles() { return matrix_.get_representative_cycles(); } template inline const typename Id_to_index_overlay::Cycle& -Id_to_index_overlay::get_representative_cycle(const Bar& bar) +Id_to_index_overlay::get_representative_cycle(const Bar& bar) { return matrix_.get_representative_cycle(bar); } template -inline void Id_to_index_overlay::swap_columns(ID_index cellID1, ID_index cellID2) +inline void Id_to_index_overlay::swap_columns(ID_index cellID1, ID_index cellID2) { matrix_.swap_columns(_id_to_index(cellID1), _id_to_index(cellID2)); std::swap(idToIndex_->at(cellID1), idToIndex_->at(cellID2)); } template -inline void Id_to_index_overlay::swap_rows(Index rowIndex1, Index rowIndex2) +inline void Id_to_index_overlay::swap_rows(Index rowIndex1, Index rowIndex2) { matrix_.swap_rows(rowIndex1, rowIndex2); } template inline typename Id_to_index_overlay::ID_index -Id_to_index_overlay::vine_swap_with_z_eq_1_case(ID_index cellID1, ID_index cellID2) +Id_to_index_overlay::vine_swap_with_z_eq_1_case(ID_index cellID1, ID_index cellID2) { Index first = _id_to_index(cellID1); Index second = _id_to_index(cellID2); @@ -1044,7 +1077,7 @@ Id_to_index_overlay::vine_swap_with_z_eq_1_cas template inline typename Id_to_index_overlay::ID_index -Id_to_index_overlay::vine_swap(ID_index cellID1, ID_index cellID2) +Id_to_index_overlay::vine_swap(ID_index cellID1, ID_index cellID2) { Index first = _id_to_index(cellID1); Index second = _id_to_index(cellID2); @@ -1068,13 +1101,13 @@ Id_to_index_overlay::vine_swap(ID_index cellID } template -inline void Id_to_index_overlay::_initialize_map([[maybe_unused]] unsigned int size) +inline void Id_to_index_overlay::_initialize_map([[maybe_unused]] unsigned int size) { if constexpr (Master_matrix::Option_list::is_of_boundary_type) { if constexpr (Master_matrix::Option_list::has_map_column_container) { idToIndex_ = new Dictionary(size); } else { - idToIndex_ = new Dictionary(size, -1); + idToIndex_ = new Dictionary(size, Master_matrix::template get_null_value()); } } else { idToIndex_ = &matrix_.pivotToColumnIndex_; @@ -1083,7 +1116,7 @@ inline void Id_to_index_overlay::_initialize_m template inline typename Id_to_index_overlay::Index -Id_to_index_overlay::_id_to_index(ID_index id) const +Id_to_index_overlay::_id_to_index(ID_index id) const { if constexpr (Master_matrix::Option_list::has_map_column_container) { return idToIndex_->at(id); @@ -1096,7 +1129,7 @@ template inline typename Id_to_index_overlay::Index& Id_to_index_overlay::_id_to_index(ID_index id) { - return idToIndex_->operator[](id); //for maps, the entry is created if not existing as needed in the constructors + return idToIndex_->operator[](id); // for maps, the entry is created if not existing as needed in the constructors } } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h b/multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h index 4a194882..7d269c39 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/Position_to_index_overlay.h @@ -31,40 +31,40 @@ namespace persistence_matrix { * @brief Overlay for @ref chainmatrix "chain matrices" replacing all input and output @ref MatIdx indices of the * original methods with @ref PosIdx indices. The overlay is useless for @ref boundarymatrix "boundary matrices" * as @ref MatIdx == @ref PosIdx for them. - * + * * @tparam %Underlying_matrix Matrix type taking the overlay. * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Position_to_index_overlay +class Position_to_index_overlay { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Row = typename Master_matrix::Row; /**< Row type, only - necessary with row access option. */ - using Bar = typename Master_matrix::Bar; /**< Bar type. */ - using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ - using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ - using Entry_representative = typename Master_matrix::Entry_representative; /**< %Entry content representative. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns - to necessary external classes. */ + using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Row = typename Master_matrix::Row; /**< Row type, only + necessary with row access option. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ + using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ + using Entry_representative = typename Master_matrix::Entry_representative; /**< %Entry content representative. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns + to necessary external classes. */ /** * @brief Constructs an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ Position_to_index_overlay(Column_settings* colSettings); /** @@ -72,34 +72,32 @@ class Position_to_index_overlay * to a column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by * increasing IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting * with 0. - * + * * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead (after creating the * matrix with the @ref Position_to_index_overlay(unsigned int, Column_settings*) * constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template - Position_to_index_overlay(const std::vector& orderedBoundaries, - Column_settings* colSettings); + Position_to_index_overlay(const std::vector& orderedBoundaries, Column_settings* colSettings); /** * @brief Constructs a new empty matrix and reserves space for the given number of columns. - * + * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - Position_to_index_overlay(unsigned int numberOfColumns, - Column_settings* colSettings); + Position_to_index_overlay(unsigned int numberOfColumns, Column_settings* colSettings); /** * @brief Only available for @ref chainmatrix "chain matrices". Constructs an empty matrix and stores the given * comparators. @@ -108,11 +106,11 @@ class Position_to_index_overlay * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -124,35 +122,35 @@ class Position_to_index_overlay */ template Position_to_index_overlay(Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** - * @brief Only available for @ref chainmatrix "chain matrices". + * @brief Only available for @ref chainmatrix "chain matrices". * Constructs a new matrix from the given ranges of @ref Matrix::Entry_representative. Each range corresponds to a * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing - * IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting with 0. + * IDs. The IDs of the simplices are also assumed to be consecutive, ordered by filtration value, starting with 0. * * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam Boundary_range Range type for @ref Matrix::Entry_representative ranges. * Assumed to have a begin(), end() and size() method. - * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a - * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, * as an ordered list of indices of its facets (again those indices correspond to their respective position - * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 - * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). - * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * in the matrix). That is why the indices of the simplices are assumed to be consecutive and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of * interest and not everything should be stored, then use the @ref insert_boundary method instead * (after creating the matrix with the @ref Position_to_index_overlay(unsigned int, Column_settings*, * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -163,9 +161,9 @@ class Position_to_index_overlay * columns. */ template - Position_to_index_overlay(const std::vector& orderedBoundaries, - Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + Position_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** * @brief Only available for @ref chainmatrix "chain matrices". @@ -175,12 +173,12 @@ class Position_to_index_overlay * And if @ref PersistenceMatrixOptions::has_vine_update is true, but * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. - * + * * @tparam BirthComparatorFunction Type of the birth comparator: (@ref Pos_index, @ref Pos_index) -> bool * @tparam DeathComparatorFunction Type of the death comparator: (@ref Pos_index, @ref Pos_index) -> bool * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if * the birth associated to the first position is strictly less than birth associated to * the second one with respect to some self defined order. It is used while swapping two unpaired or @@ -191,32 +189,33 @@ class Position_to_index_overlay * columns. */ template - Position_to_index_overlay(unsigned int numberOfColumns, + Position_to_index_overlay(unsigned int numberOfColumns, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator); /** * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept * instead of the one in the copied matrix. - * + * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ - Position_to_index_overlay(const Position_to_index_overlay& matrixToCopy, - Column_settings* colSettings = nullptr); + Position_to_index_overlay(const Position_to_index_overlay& matrixToCopy, Column_settings* colSettings = nullptr); /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Position_to_index_overlay(Position_to_index_overlay&& other) noexcept; + ~Position_to_index_overlay() = default; + /** - * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. - * This means that it is assumed that this method is called on boundaries in the order of the filtration. - * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the cells in the given boundary are identified by their relative position in the filtration, * starting at 0. If it is not the case, use the other * @ref insert_boundary(ID_index, const Boundary_range&, Dimension) "insert_boundary" instead by indicating the * cell ID used in the boundaries when the cell is inserted. @@ -226,15 +225,16 @@ class Position_to_index_overlay * * When inserted, the given boundary is reduced and from the reduction process, the column is deduced in the form of: * `IDIdx + linear combination of older column IDIdxs`. If the barcode is stored, it will be updated. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param boundary Boundary generating the new column. The content should be ordered by ID. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief It does the same as the other version, but allows the boundary cells to be identified without restrictions * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then @@ -242,30 +242,32 @@ class Position_to_index_overlay * * As a cell has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that * the cells are inserted by order of filtration), it is sufficient to indicate the ID of the cell being inserted. - * + * * @tparam Boundary_range Range of @ref Matrix::Entry_representative. Assumed to have a begin(), end() and size() * method. * @param cellIndex @ref IDIdx index to use to identify the new cell. - * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the - * @p cellID values of precedent calls of the method for the corresponding cells and should be ordered in + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p cellID values of precedent calls of the method for the corresponding cells and should be ordered in * increasing order. - * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, + * @param dim Dimension of the cell whose boundary is given. If the complex is simplicial, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(ID_index cellIndex, + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref PosIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param position @ref PosIdx index of the column to return. * @return Reference to the column. */ Column& get_column(Pos_index position); /** * @brief Returns the column at the given @ref PosIdx index. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. - * + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. + * * @param position @ref PosIdx index of the column to return. * @return Const reference to the column. */ @@ -273,8 +275,8 @@ class Position_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. * Returns the row at the given @ref rowindex "row index". - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. - * + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * * @param rowIndex @ref rowindex "Row index" of the row to return. * @return Reference to the row. */ @@ -282,8 +284,8 @@ class Position_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. * Returns the row at the given @ref rowindex "row index". - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. - * + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * * @param rowIndex @ref rowindex "Row index" of the row to return. * @return Const reference to the row. */ @@ -291,15 +293,15 @@ class Position_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::has_row_access and * @ref PersistenceMatrixOptions::has_removable_rows are true. - * Assumes that the row is empty and removes it. + * Assumes that the row is empty and removes it. * * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row entries are not - * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when * the column entries are destroyed later. The row access is just meant as a "read only" access to the rows and the * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from * dictionaries. This allows to avoid testing the emptiness of a row at each column entry removal, what can be - * quite frequent. - * + * quite frequent. + * * @param rowIndex @ref rowindex "Row index" of the empty row to remove. */ void erase_empty_row(ID_index rowIndex); @@ -313,7 +315,7 @@ class Position_to_index_overlay * Also updates the barcode if it was computed. * * See also @ref remove_last. - * + * * @param position @ref PosIdx index of the cell to remove. */ void remove_maximal_cell(Pos_index position); @@ -328,21 +330,21 @@ class Position_to_index_overlay void remove_last(); /** - * @brief Returns the maximal dimension of a cell stored in the matrix. Only available + * @brief Returns the maximal dimension of a cell stored in the matrix. Only available * if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access is true. - * + * * @return The maximal dimension. */ Dimension get_max_dimension() const; /** * @brief Returns the current number of columns in the matrix. - * + * * @return The number of columns. */ Index get_number_of_columns() const; /** * @brief Returns the dimension of the given cell. - * + * * @param position @ref PosIdx index of the cell. * @return Dimension of the cell. */ @@ -354,7 +356,7 @@ class Position_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param sourcePosition @ref PosIdx index of the source column. * @param targetPosition @ref PosIdx index of the target column. */ @@ -366,14 +368,12 @@ class Position_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param sourcePosition @ref PosIdx index of the source column. * @param coefficient Value to multiply. * @param targetPosition @ref PosIdx index of the target column. */ - void multiply_target_and_add_to(Pos_index sourcePosition, - const Field_element& coefficient, - Pos_index targetPosition); + void multiply_target_and_add_to(Pos_index sourcePosition, const Field_element& coefficient, Pos_index targetPosition); /** * @brief Multiplies the source column with the coefficient before adding it to the target column. * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. @@ -381,18 +381,16 @@ class Position_to_index_overlay * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. * So should be used with care. - * + * * @param coefficient Value to multiply. * @param sourcePosition @ref PosIdx index of the source column. * @param targetPosition @ref PosIdx index of the target column. */ - void multiply_source_and_add_to(const Field_element& coefficient, - Pos_index sourcePosition, - Pos_index targetPosition); + void multiply_source_and_add_to(const Field_element& coefficient, Pos_index sourcePosition, Pos_index targetPosition); /** * @brief Indicates if the entry at given coordinates has value zero. - * + * * @param position @ref PosIdx index of the cell corresponding to the column of the entry. * @param rowIndex @ref rowindex "Row index" of the row of the entry. * @return true If the entry has value zero. @@ -404,7 +402,7 @@ class Position_to_index_overlay * * Note that this method should always return false, as a valid @ref chainmatrix "chain matrix" never has * empty columns. - * + * * @param position @ref PosIdx index of the cell corresponding to the column. * @return true If the column has value zero. * @return false Otherwise. @@ -414,14 +412,14 @@ class Position_to_index_overlay /** * @brief Returns the @ref PosIdx index of the column which has the given @ref rowindex "row index" as pivot. * Assumes that the pivot exists. - * + * * @param cellIndex @ref rowindex "Row index" of the pivot. * @return @ref PosIdx index of the column with the given pivot. */ Pos_index get_column_with_pivot(ID_index cellIndex) const; // assumes that pivot exists /** * @brief Returns the @ref rowindex "row index" of the pivot of the given column. - * + * * @param position @ref PosIdx index of the cell corresponding to the column. * @return The @ref rowindex "row index" of the pivot. */ @@ -429,11 +427,12 @@ class Position_to_index_overlay /** * @brief Resets the matrix to an empty matrix. - * + * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { matrix_.reset(colSettings); positionToIndex_.clear(); nextPosition_ = 0; @@ -445,11 +444,17 @@ class Position_to_index_overlay /** * @brief Assign operator. */ - Position_to_index_overlay& operator=(const Position_to_index_overlay& other); + Position_to_index_overlay& operator=(const Position_to_index_overlay& other) = default; + /** + * @brief Move assign operator. + */ + Position_to_index_overlay& operator=(Position_to_index_overlay&& other) noexcept = default; + /** * @brief Swap operator. */ - friend void swap(Position_to_index_overlay& matrix1, Position_to_index_overlay& matrix2) { + friend void swap(Position_to_index_overlay& matrix1, Position_to_index_overlay& matrix2) noexcept + { swap(matrix1.matrix_, matrix2.matrix_); matrix1.positionToIndex_.swap(matrix2.positionToIndex_); std::swap(matrix1.nextPosition_, matrix2.nextPosition_); @@ -465,12 +470,12 @@ class Position_to_index_overlay * Available only if @ref PersistenceMatrixOptions::has_column_pairings is true. * * Recall that we assume that the boundaries were inserted in the order of filtration for the barcode to be valid. - * - * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three attributes: * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. */ const Barcode& get_current_barcode() const; - + /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Pre-computes * the representative cycles of the current state of the filtration represented by the matrix. @@ -482,24 +487,24 @@ class Position_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. * Returns all representative cycles of the current filtration. - * + * * @return A const reference to the vector of representative cycles. */ const std::vector& get_representative_cycles(); /** * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. * Returns the cycle representing the given bar. - * + * * @param bar A bar from the current barcode. * @return A const reference to the cycle representing @p bar. */ const Cycle& get_representative_cycle(const Bar& bar); - + /** * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. * Does the same than @ref vine_swap, but assumes that the swap is non trivial and * therefore skips a part of the case study. - * + * * @param position @ref PosIdx index of the first cell to swap. The second one has to be at `position + 1`. * @return true If the barcode changed from the swap. * @return false Otherwise. @@ -508,12 +513,12 @@ class Position_to_index_overlay /** * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. * Does a vine swap between two cells which are consecutive in the filtration. Roughly, if \f$ F \f$ is the current - * filtration represented by the matrix, the method modifies the matrix such that the new state corresponds to + * filtration represented by the matrix, the method modifies the matrix such that the new state corresponds to * a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but with the two cells at position `position` * and `position + 1` swapped. Of course, the two cells should not have a face/coface relation which each other ; * \f$ F' \f$ has to be a valid filtration. * See @cite vineyards for more information about vine and vineyards. - * + * * @param position @ref PosIdx index of the first cell to swap. The second one has to be at `position + 1`. * @return true If the barcode changed from the swap. * @return false Otherwise. @@ -521,26 +526,27 @@ class Position_to_index_overlay bool vine_swap(Pos_index position); private: - Underlying_matrix matrix_; /**< Interfaced matrix. */ - std::vector positionToIndex_; /**< Map from @ref PosIdx index to @ref MatIdx index. */ - Pos_index nextPosition_; /**< Next unused position. */ - Index nextIndex_; /**< Next unused index. */ + Underlying_matrix matrix_; /**< Interfaced matrix. */ + std::vector positionToIndex_; /**< Map from @ref PosIdx index to @ref MatIdx index. */ + Pos_index nextPosition_; /**< Next unused position. */ + Index nextIndex_; /**< Next unused index. */ }; template inline Position_to_index_overlay::Position_to_index_overlay( Column_settings* colSettings) - : matrix_(colSettings), nextPosition_(0), nextIndex_(0) + : matrix_(colSettings), nextPosition_(0), nextIndex_(0) {} template template inline Position_to_index_overlay::Position_to_index_overlay( - const std::vector& orderedBoundaries, Column_settings* colSettings) + const std::vector& orderedBoundaries, + Column_settings* colSettings) : matrix_(orderedBoundaries, colSettings), positionToIndex_(orderedBoundaries.size()), nextPosition_(orderedBoundaries.size()), - nextIndex_(orderedBoundaries.size()) + nextIndex_(orderedBoundaries.size()) { for (Index i = 0; i < orderedBoundaries.size(); i++) { positionToIndex_[i] = i; @@ -549,33 +555,31 @@ inline Position_to_index_overlay::Position_to_ template inline Position_to_index_overlay::Position_to_index_overlay( - unsigned int numberOfColumns, Column_settings* colSettings) - : matrix_(numberOfColumns, colSettings), - positionToIndex_(numberOfColumns), - nextPosition_(0), - nextIndex_(0) + unsigned int numberOfColumns, + Column_settings* colSettings) + : matrix_(numberOfColumns, colSettings), positionToIndex_(numberOfColumns), nextPosition_(0), nextIndex_(0) {} template template inline Position_to_index_overlay::Position_to_index_overlay( - Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) - : matrix_(colSettings, birthComparator, deathComparator), nextPosition_(0), nextIndex_(0) + : matrix_(colSettings, birthComparator, deathComparator), nextPosition_(0), nextIndex_(0) {} template template inline Position_to_index_overlay::Position_to_index_overlay( - const std::vector& orderedBoundaries, + const std::vector& orderedBoundaries, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) : matrix_(orderedBoundaries, colSettings, birthComparator, deathComparator), positionToIndex_(orderedBoundaries.size()), nextPosition_(orderedBoundaries.size()), - nextIndex_(orderedBoundaries.size()) + nextIndex_(orderedBoundaries.size()) { for (Index i = 0; i < orderedBoundaries.size(); i++) { positionToIndex_[i] = i; @@ -585,23 +589,24 @@ inline Position_to_index_overlay::Position_to_ template template inline Position_to_index_overlay::Position_to_index_overlay( - unsigned int numberOfColumns, + unsigned int numberOfColumns, Column_settings* colSettings, - const BirthComparatorFunction& birthComparator, + const BirthComparatorFunction& birthComparator, const DeathComparatorFunction& deathComparator) : matrix_(numberOfColumns, colSettings, birthComparator, deathComparator), positionToIndex_(numberOfColumns), nextPosition_(0), - nextIndex_(0) + nextIndex_(0) {} template inline Position_to_index_overlay::Position_to_index_overlay( - const Position_to_index_overlay& matrixToCopy, Column_settings* colSettings) + const Position_to_index_overlay& matrixToCopy, + Column_settings* colSettings) : matrix_(matrixToCopy.matrix_, colSettings), positionToIndex_(matrixToCopy.positionToIndex_), nextPosition_(matrixToCopy.nextPosition_), - nextIndex_(matrixToCopy.nextIndex_) + nextIndex_(matrixToCopy.nextIndex_) {} template @@ -610,16 +615,16 @@ inline Position_to_index_overlay::Position_to_ : matrix_(std::move(other.matrix_)), positionToIndex_(std::move(other.positionToIndex_)), nextPosition_(std::exchange(other.nextPosition_, 0)), - nextIndex_(std::exchange(other.nextIndex_, 0)) + nextIndex_(std::exchange(other.nextIndex_, 0)) {} template template inline void Position_to_index_overlay::insert_boundary(const Boundary_range& boundary, - Dimension dim) + Dimension dim) { if (positionToIndex_.size() <= nextPosition_) { - positionToIndex_.resize(nextPosition_ * 2 + 1); + positionToIndex_.resize((nextPosition_ * 2) + 1); } positionToIndex_[nextPosition_++] = nextIndex_++; @@ -630,11 +635,11 @@ inline void Position_to_index_overlay::insert_ template template inline void Position_to_index_overlay::insert_boundary(ID_index cellIndex, - const Boundary_range& boundary, - Dimension dim) + const Boundary_range& boundary, + Dimension dim) { if (positionToIndex_.size() <= nextPosition_) { - positionToIndex_.resize(nextPosition_ * 2 + 1); + positionToIndex_.resize((nextPosition_ * 2) + 1); } positionToIndex_[nextPosition_++] = nextIndex_++; @@ -644,40 +649,40 @@ inline void Position_to_index_overlay::insert_ template inline typename Position_to_index_overlay::Column& -Position_to_index_overlay::get_column(Pos_index position) +Position_to_index_overlay::get_column(Pos_index position) { return matrix_.get_column(positionToIndex_[position]); } template inline const typename Position_to_index_overlay::Column& -Position_to_index_overlay::get_column(Pos_index position) const +Position_to_index_overlay::get_column(Pos_index position) const { return matrix_.get_column(positionToIndex_[position]); } template inline typename Position_to_index_overlay::Row& -Position_to_index_overlay::get_row(ID_index rowIndex) +Position_to_index_overlay::get_row(ID_index rowIndex) { return matrix_.get_row(rowIndex); } template inline const typename Position_to_index_overlay::Row& -Position_to_index_overlay::get_row(ID_index rowIndex) const +Position_to_index_overlay::get_row(ID_index rowIndex) const { return matrix_.get_row(rowIndex); } template -inline void Position_to_index_overlay::erase_empty_row(ID_index rowIndex) +inline void Position_to_index_overlay::erase_empty_row(ID_index rowIndex) { return matrix_.erase_empty_row(rowIndex); } template -inline void Position_to_index_overlay::remove_maximal_cell(Pos_index position) +inline void Position_to_index_overlay::remove_maximal_cell(Pos_index position) { --nextPosition_; @@ -697,7 +702,7 @@ inline void Position_to_index_overlay::remove_ } template -inline void Position_to_index_overlay::remove_last() +inline void Position_to_index_overlay::remove_last() { --nextPosition_; if constexpr (Master_matrix::Option_list::has_vine_update) { @@ -710,66 +715,68 @@ inline void Position_to_index_overlay::remove_ template inline typename Position_to_index_overlay::Dimension -Position_to_index_overlay::get_max_dimension() const +Position_to_index_overlay::get_max_dimension() const { return matrix_.get_max_dimension(); } template inline typename Position_to_index_overlay::Index -Position_to_index_overlay::get_number_of_columns() const +Position_to_index_overlay::get_number_of_columns() const { return matrix_.get_number_of_columns(); } template inline typename Position_to_index_overlay::Dimension -Position_to_index_overlay::get_column_dimension(Pos_index position) const +Position_to_index_overlay::get_column_dimension(Pos_index position) const { return matrix_.get_column_dimension(positionToIndex_[position]); } template inline void Position_to_index_overlay::add_to(Pos_index sourcePosition, - Pos_index targetPosition) + Pos_index targetPosition) { return matrix_.add_to(positionToIndex_[sourcePosition], positionToIndex_[targetPosition]); } template inline void Position_to_index_overlay::multiply_target_and_add_to( - Pos_index sourcePosition, const Field_element& coefficient, Pos_index targetPosition) + Pos_index sourcePosition, + const Field_element& coefficient, + Pos_index targetPosition) { - return matrix_.multiply_target_and_add_to(positionToIndex_[sourcePosition], - coefficient, - positionToIndex_[targetPosition]); + return matrix_.multiply_target_and_add_to( + positionToIndex_[sourcePosition], coefficient, positionToIndex_[targetPosition]); } template inline void Position_to_index_overlay::multiply_source_and_add_to( - const Field_element& coefficient, Pos_index sourcePosition, Pos_index targetPosition) + const Field_element& coefficient, + Pos_index sourcePosition, + Pos_index targetPosition) { - return matrix_.multiply_source_and_add_to(coefficient, - positionToIndex_[sourcePosition], - positionToIndex_[targetPosition]); + return matrix_.multiply_source_and_add_to( + coefficient, positionToIndex_[sourcePosition], positionToIndex_[targetPosition]); } template inline bool Position_to_index_overlay::is_zero_entry(Pos_index position, - ID_index rowIndex) const + ID_index rowIndex) const { return matrix_.is_zero_entry(positionToIndex_[position], rowIndex); } template -inline bool Position_to_index_overlay::is_zero_column(Pos_index position) +inline bool Position_to_index_overlay::is_zero_column(Pos_index position) { return matrix_.is_zero_column(positionToIndex_[position]); } template inline typename Position_to_index_overlay::Pos_index -Position_to_index_overlay::get_column_with_pivot(ID_index cellIndex) const +Position_to_index_overlay::get_column_with_pivot(ID_index cellIndex) const { Index id = matrix_.get_column_with_pivot(cellIndex); Pos_index i = 0; @@ -779,58 +786,46 @@ Position_to_index_overlay::get_column_with_piv template inline typename Position_to_index_overlay::ID_index -Position_to_index_overlay::get_pivot(Pos_index position) +Position_to_index_overlay::get_pivot(Pos_index position) { return matrix_.get_pivot(positionToIndex_[position]); } template -inline Position_to_index_overlay& -Position_to_index_overlay::operator=(const Position_to_index_overlay& other) -{ - matrix_ = other.matrix_; - positionToIndex_ = other.positionToIndex_; - nextPosition_ = other.nextPosition_; - nextIndex_ = other.nextIndex_; - - return *this; -} - -template -inline void Position_to_index_overlay::print() +inline void Position_to_index_overlay::print() { return matrix_.print(); } template inline const typename Position_to_index_overlay::Barcode& -Position_to_index_overlay::get_current_barcode() const +Position_to_index_overlay::get_current_barcode() const { return matrix_.get_current_barcode(); } template -inline void Position_to_index_overlay::update_representative_cycles() +inline void Position_to_index_overlay::update_representative_cycles() { matrix_.update_representative_cycles(); } template inline const std::vector::Cycle>& -Position_to_index_overlay::get_representative_cycles() +Position_to_index_overlay::get_representative_cycles() { return matrix_.get_representative_cycles(); } template inline const typename Position_to_index_overlay::Cycle& -Position_to_index_overlay::get_representative_cycle(const Bar& bar) +Position_to_index_overlay::get_representative_cycle(const Bar& bar) { return matrix_.get_representative_cycle(bar); } template -inline bool Position_to_index_overlay::vine_swap_with_z_eq_1_case(Pos_index position) +inline bool Position_to_index_overlay::vine_swap_with_z_eq_1_case(Pos_index position) { Index next = matrix_.vine_swap_with_z_eq_1_case(positionToIndex_[position], positionToIndex_[position + 1]); if (next == positionToIndex_[position]) { @@ -842,7 +837,7 @@ inline bool Position_to_index_overlay::vine_sw } template -inline bool Position_to_index_overlay::vine_swap(Pos_index position) +inline bool Position_to_index_overlay::vine_swap(Pos_index position) { Index next = matrix_.vine_swap(positionToIndex_[position], positionToIndex_[position + 1]); if (next == positionToIndex_[position]) { diff --git a/multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h b/multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h index 2b2a3360..34f83ee6 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/RU_matrix.h @@ -17,13 +17,18 @@ #ifndef PM_RU_MATRIX_H #define PM_RU_MATRIX_H +#include //std::conditional +#include //std::swap, std::move & std::exchange +#include //print() only #include -#include //std::swap, std::move & std::exchange -#include //print() only +#include namespace Gudhi { namespace persistence_matrix { +template +class RU_pairing; + /** * @class RU_matrix RU_matrix.h gudhi/Persistence_matrix/RU_matrix.h * @ingroup persistence_matrix @@ -40,29 +45,34 @@ class RU_matrix : public Master_matrix::RU_pairing_option, public Master_matrix::RU_vine_swap_option, public Master_matrix::RU_representative_cycles_option { + private: + using Pair_opt = typename Master_matrix::RU_pairing_option; + using Swap_opt = typename Master_matrix::RU_vine_swap_option; + using Rep_opt = typename Master_matrix::RU_representative_cycles_option; + public: /** * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. */ using Field_operators = typename Master_matrix::Field_operators; - using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - using Row = typename Master_matrix::Row; /**< Row type, - only necessary with row access option. */ - using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ - using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to - necessary external classes. */ - using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Field_element = typename Master_matrix::Element; /**< Type of an field element. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using Row = typename Master_matrix::Row; /**< Row type, + only necessary with row access option. */ + using Entry_constructor = typename Master_matrix::Entry_constructor; /**< Factory of @ref Entry classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + using Boundary = typename Master_matrix::Boundary; /**< Type of an input column. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Constructs an empty matrix. * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ RU_matrix(Column_settings* colSettings); /** @@ -83,7 +93,7 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * interest and not everything should be stored, then use the @ref insert_boundary method instead (after creating the * matrix with the @ref RU_matrix(unsigned int, Column_settings*) constructor preferably). * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ template RU_matrix(const std::vector& orderedBoundaries, Column_settings* colSettings); @@ -92,7 +102,7 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * * @param numberOfColumns Number of columns to reserve space for. * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ RU_matrix(unsigned int numberOfColumns, Column_settings* colSettings); /** @@ -101,7 +111,7 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * * @param matrixToCopy Matrix to copy. * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. - * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * The structure should contain all the necessary external classes specifically necessary for the chosen column type, * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. */ RU_matrix(const RU_matrix& matrixToCopy, Column_settings* colSettings = nullptr); @@ -112,6 +122,8 @@ class RU_matrix : public Master_matrix::RU_pairing_option, */ RU_matrix(RU_matrix&& other) noexcept; + ~RU_matrix() = default; + /** * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. * This means that it is assumed that this method is called on boundaries in the order of the filtration. @@ -132,7 +144,8 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief It does the same as the other version, but allows the boundary cells to be identified without restrictions * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then @@ -151,11 +164,13 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * this parameter can be omitted as it can be deduced from the size of the boundary. */ template - void insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim = -1); + void insert_boundary(ID_index cellIndex, + const Boundary_range& boundary, + Dimension dim = Master_matrix::template get_null_value()); /** * @brief Returns the column at the given @ref MatIdx index in \f$ R \f$ if @p inR is true and * in \f$ U \f$ if @p inR is false. - * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * The type of the column depends on the chosen options, see @ref PersistenceMatrixOptions::column_type. * * Note that before returning the column, all column entries can eventually be reordered, if lazy swaps occurred. * It is therefore recommended to avoid calling @ref get_column between vine swaps, otherwise the benefits @@ -170,7 +185,7 @@ class RU_matrix : public Master_matrix::RU_pairing_option, /** * @brief Returns the row at the given @ref rowindex "row index" in \f$ R \f$ if @p inR is true and * in \f$ U \f$ if @p inR is false. - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. * * Note that before returning the row, all column entries can eventually be reordered, if lazy swaps occurred. * It is therefore recommended to avoid calling @ref get_row between vine swaps, otherwise the benefits @@ -347,13 +362,17 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * @brief Resets the matrix to an empty matrix. * * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all - * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * the necessary external classes specifically necessary for the chosen column type, such as custom allocators. */ - void reset(Column_settings* colSettings) { + void reset(Column_settings* colSettings) + { + if constexpr (Master_matrix::Option_list::has_column_pairings) Pair_opt::_reset(); + if constexpr (Master_matrix::Option_list::can_retrieve_representative_cycles) Rep_opt::_reset(); reducedMatrixR_.reset(colSettings); mirrorMatrixU_.reset(colSettings); pivotToColumnIndex_.clear(); nextEventIndex_ = 0; + positionToID_.clear(); if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -363,46 +382,50 @@ class RU_matrix : public Master_matrix::RU_pairing_option, * @brief Assign operator. */ RU_matrix& operator=(const RU_matrix& other); + /** + * @brief Move assign operator. + */ + RU_matrix& operator=(RU_matrix&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(RU_matrix& matrix1, RU_matrix& matrix2) { - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); - swap(static_cast(matrix1), - static_cast(matrix2)); + friend void swap(RU_matrix& matrix1, RU_matrix& matrix2) noexcept + { + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); + swap(static_cast(matrix1), static_cast(matrix2)); swap(matrix1.reducedMatrixR_, matrix2.reducedMatrixR_); swap(matrix1.mirrorMatrixU_, matrix2.mirrorMatrixU_); matrix1.pivotToColumnIndex_.swap(matrix2.pivotToColumnIndex_); std::swap(matrix1.nextEventIndex_, matrix2.nextEventIndex_); + matrix1.positionToID_.swap(matrix2.positionToID_); std::swap(matrix1.operators_, matrix2.operators_); } - void print(Index startCol = 0, Index endCol = -1, Index startRow = 0, Index endRow = -1); // for debug + void print(); // for debug private: - using Swap_opt = typename Master_matrix::RU_vine_swap_option; - using Pair_opt = typename Master_matrix::RU_pairing_option; - using Rep_opt = typename Master_matrix::RU_representative_cycles_option; - using Dictionary = typename Master_matrix::template Dictionary; + using Pivot_dictionary = typename Master_matrix::template Dictionary; + using Position_dictionary = std::unordered_map; // TODO: try other type of maps? using Barcode = typename Master_matrix::Barcode; using Bar_dictionary = typename Master_matrix::Bar_dictionary; using R_matrix = typename Master_matrix::Master_boundary_matrix; using U_matrix = typename Master_matrix::Master_base_matrix; - friend Rep_opt; // direct access to the two matrices - friend Swap_opt; // direct access to the two matrices + friend Rep_opt; // direct access to the two matrices + friend Swap_opt; // direct access to the two matrices, pivotToColumnIndex_ + friend RU_pairing; // direct access to positionToID_ - R_matrix reducedMatrixR_; /**< R. */ + R_matrix reducedMatrixR_; /**< R. */ // TODO: make U not accessible by default and add option to enable access? Inaccessible, it // needs less options and we could avoid some ifs. - U_matrix mirrorMatrixU_; /**< U. */ - Dictionary pivotToColumnIndex_; /**< Map from pivot row index to column @ref MatIdx index. */ - Pos_index nextEventIndex_; /**< Next birth or death index. */ - Field_operators* operators_; /**< Field operators, - can be nullptr if @ref PersistenceMatrixOptions::is_z2 is true. */ + U_matrix mirrorMatrixU_; /**< U. */ + Pivot_dictionary pivotToColumnIndex_; /**< Map from pivot row index to column @ref MatIdx index. */ + Pos_index nextEventIndex_; /**< Next birth or death index. */ + Position_dictionary positionToID_; /**< Map from @ref MatIdx to @ref IDIdx. */ + Field_operators* operators_; /**< Field operators, can be nullptr if + @ref PersistenceMatrixOptions::is_z2 is true. */ void _insert_boundary(Index currentIndex); void _initialize_U(); @@ -410,11 +433,10 @@ class RU_matrix : public Master_matrix::RU_pairing_option, void _reduce_last_column(Index lastIndex); void _reduce_column(Index target, Index eventIndex); void _reduce_column_by(Index target, Index source); + Index _get_column_with_pivot(ID_index pivot) const; void _update_barcode(ID_index birthPivot, Pos_index death); void _add_bar(Dimension dim, Pos_index birth); void _remove_last_in_barcode(Pos_index eventIndex); - - constexpr Bar_dictionary& _indexToBar(); }; template @@ -451,7 +473,7 @@ inline RU_matrix::RU_matrix(const std::vector& or if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(orderedBoundaries.size()); } else { - pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + pivotToColumnIndex_.resize(orderedBoundaries.size(), Master_matrix::template get_null_value()); } _initialize_U(); @@ -466,6 +488,7 @@ inline RU_matrix::RU_matrix(unsigned int numberOfColumns, Column_ reducedMatrixR_(numberOfColumns, colSettings), mirrorMatrixU_(numberOfColumns, colSettings), nextEventIndex_(0), + positionToID_(numberOfColumns), operators_(nullptr) { if constexpr (!Master_matrix::Option_list::is_z2) { @@ -475,13 +498,10 @@ inline RU_matrix::RU_matrix(unsigned int numberOfColumns, Column_ if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.reserve(numberOfColumns); } else { - pivotToColumnIndex_.resize(numberOfColumns, -1); + pivotToColumnIndex_.resize(numberOfColumns, Master_matrix::template get_null_value()); } if constexpr (Master_matrix::Option_list::has_column_pairings) { - _indexToBar().reserve(numberOfColumns); - } - if constexpr (Master_matrix::Option_list::has_vine_update) { - Swap_opt::_positionToRowIdx().reserve(numberOfColumns); + Pair_opt::_reserve(numberOfColumns); } } @@ -494,6 +514,7 @@ inline RU_matrix::RU_matrix(const RU_matrix& matrixToCopy, Column mirrorMatrixU_(matrixToCopy.mirrorMatrixU_, colSettings), pivotToColumnIndex_(matrixToCopy.pivotToColumnIndex_), nextEventIndex_(matrixToCopy.nextEventIndex_), + positionToID_(matrixToCopy.positionToID_), operators_(colSettings == nullptr ? matrixToCopy.operators_ : nullptr) { if constexpr (!Master_matrix::Option_list::is_z2) { @@ -510,6 +531,7 @@ inline RU_matrix::RU_matrix(RU_matrix&& other) noexcept mirrorMatrixU_(std::move(other.mirrorMatrixU_)), pivotToColumnIndex_(std::move(other.pivotToColumnIndex_)), nextEventIndex_(std::exchange(other.nextEventIndex_, 0)), + positionToID_(std::move(other.positionToID_)), operators_(std::exchange(other.operators_, nullptr)) {} @@ -522,27 +544,16 @@ inline void RU_matrix::insert_boundary(const Boundary_range& boun template template -inline void RU_matrix::insert_boundary(ID_index cellIndex, - const Boundary_range& boundary, - Dimension dim) +inline void RU_matrix::insert_boundary(ID_index cellIndex, const Boundary_range& boundary, Dimension dim) { // maps for possible shifting between column content and position indices used for birth events - if constexpr (Master_matrix::Option_list::has_column_pairings && !Master_matrix::Option_list::has_vine_update) { - if (cellIndex != nextEventIndex_) { - Pair_opt::idToPosition_.emplace(cellIndex, nextEventIndex_); - if constexpr (Master_matrix::Option_list::has_removable_columns) { - Pair_opt::PIDM::map_.emplace(nextEventIndex_, cellIndex); - } - } - } - if constexpr (Master_matrix::Option_list::has_vine_update) { - if (cellIndex != nextEventIndex_) { - Swap_opt::_positionToRowIdx().emplace(nextEventIndex_, cellIndex); - if constexpr (Master_matrix::Option_list::has_column_pairings) { - Swap_opt::template RU_pairing::idToPosition_.emplace(cellIndex, nextEventIndex_); - } + if (cellIndex != nextEventIndex_) { + positionToID_.emplace(nextEventIndex_, cellIndex); + if constexpr (Master_matrix::Option_list::has_column_pairings) { + Pair_opt::_insert_id_position(cellIndex, nextEventIndex_); } } + _insert_boundary(reducedMatrixR_.insert_boundary(cellIndex, boundary, dim)); } @@ -604,13 +615,14 @@ inline void RU_matrix::remove_last() pivotToColumnIndex_.erase(reducedMatrixR_.remove_last()); } else { ID_index lastPivot = reducedMatrixR_.remove_last(); - if (lastPivot != static_cast(-1)) pivotToColumnIndex_[lastPivot] = -1; + if (lastPivot != Master_matrix::template get_null_value()) + pivotToColumnIndex_[lastPivot] = Master_matrix::template get_null_value(); } - // if has_vine_update and has_column_pairings are both true, - // then the element is already removed in _remove_last_in_barcode - if constexpr (Master_matrix::Option_list::has_vine_update && !Master_matrix::Option_list::has_column_pairings) { - Swap_opt::_positionToRowIdx().erase(nextEventIndex_); + // if has_column_pairings is true, then the element is already removed in _remove_last_in_barcode + // to avoid a second "find" + if constexpr (!Master_matrix::Option_list::has_column_pairings) { + positionToID_.erase(nextEventIndex_); } } @@ -641,7 +653,8 @@ inline void RU_matrix::add_to(Index sourceColumnIndex, Index targ if constexpr (Master_matrix::Option_list::is_z2) mirrorMatrixU_.add_to(targetColumnIndex, sourceColumnIndex); else - mirrorMatrixU_.add_to(sourceColumnIndex, targetColumnIndex); + mirrorMatrixU_.multiply_source_and_add_to( + operators_->get_characteristic() - 1, targetColumnIndex, sourceColumnIndex); } template @@ -649,10 +662,10 @@ inline void RU_matrix::multiply_target_and_add_to(Index sourceCol const Field_element& coefficient, Index targetColumnIndex) { - static_assert(!Master_matrix::Option_list::is_z2, - "Multiplication with something else than the identity is not allowed with Z2 coefficients."); reducedMatrixR_.multiply_target_and_add_to(sourceColumnIndex, coefficient, targetColumnIndex); - mirrorMatrixU_.multiply_target_and_add_to(sourceColumnIndex, coefficient, targetColumnIndex); + // U transposed to avoid row operations + mirrorMatrixU_.get_column(targetColumnIndex) *= coefficient; + mirrorMatrixU_.multiply_source_and_add_to(operators_->get_characteristic() - 1, targetColumnIndex, sourceColumnIndex); } template @@ -660,10 +673,14 @@ inline void RU_matrix::multiply_source_and_add_to(const Field_ele Index sourceColumnIndex, Index targetColumnIndex) { - static_assert(!Master_matrix::Option_list::is_z2, - "Multiplication with something else than the identity is not allowed with Z2 coefficients."); reducedMatrixR_.multiply_source_and_add_to(coefficient, sourceColumnIndex, targetColumnIndex); - mirrorMatrixU_.multiply_source_and_add_to(coefficient, sourceColumnIndex, targetColumnIndex); + // U transposed to avoid row operations + if constexpr (Master_matrix::Option_list::is_z2) { + if (coefficient) mirrorMatrixU_.add_to(targetColumnIndex, sourceColumnIndex); + } else { + mirrorMatrixU_.multiply_source_and_add_to( + operators_->get_characteristic() - coefficient, targetColumnIndex, sourceColumnIndex); + } } template @@ -721,6 +738,8 @@ inline typename RU_matrix::Index RU_matrix::get_pi template inline RU_matrix& RU_matrix::operator=(const RU_matrix& other) { + if (this == &other) return *this; + Swap_opt::operator=(other); Pair_opt::operator=(other); Rep_opt::operator=(other); @@ -728,17 +747,34 @@ inline RU_matrix& RU_matrix::operator=(const RU_ma mirrorMatrixU_ = other.mirrorMatrixU_; pivotToColumnIndex_ = other.pivotToColumnIndex_; nextEventIndex_ = other.nextEventIndex_; + positionToID_ = other.positionToID_; operators_ = other.operators_; + return *this; } template -inline void RU_matrix::print(Index startCol, Index endCol, Index startRow, Index endRow) +inline RU_matrix& RU_matrix::operator=(RU_matrix&& other) noexcept +{ + Pair_opt::operator=(std::move(other)); + Swap_opt::operator=(std::move(other)); + Rep_opt::operator=(std::move(other)); + + reducedMatrixR_ = std::move(other.reducedMatrixR_); + mirrorMatrixU_ = std::move(other.mirrorMatrixU_); + pivotToColumnIndex_ = std::move(other.pivotToColumnIndex_); + nextEventIndex_ = std::exchange(other.nextEventIndex_, 0); + positionToID_ = std::move(other.positionToID_); + operators_ = std::exchange(other.operators_, nullptr); +} + +template +inline void RU_matrix::print() { std::cout << "R_matrix:\n"; - reducedMatrixR_.print(startCol, endCol, startRow, endRow); + reducedMatrixR_.print(); std::cout << "U_matrix:\n"; - mirrorMatrixU_.print(startCol, endCol, startCol, endCol); + mirrorMatrixU_.print(); } template @@ -752,8 +788,8 @@ inline void RU_matrix::_insert_boundary(Index currentIndex) if constexpr (!Master_matrix::Option_list::has_map_column_container) { ID_index pivot = reducedMatrixR_.get_column(currentIndex).get_pivot(); - if (pivot != static_cast(-1) && pivotToColumnIndex_.size() <= pivot) - pivotToColumnIndex_.resize((pivot + 1) * 2, -1); + if (pivot != Master_matrix::template get_null_value() && pivotToColumnIndex_.size() <= pivot) + pivotToColumnIndex_.resize((pivot + 1) * 2, Master_matrix::template get_null_value()); } _reduce_last_column(currentIndex); @@ -779,7 +815,7 @@ template inline void RU_matrix::_reduce() { if constexpr (Master_matrix::Option_list::has_column_pairings) { - _indexToBar().reserve(reducedMatrixR_.get_number_of_columns()); + Pair_opt::_reserve(reducedMatrixR_.get_number_of_columns()); } for (Index i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { @@ -805,30 +841,18 @@ inline void RU_matrix::_reduce_last_column(Index lastIndex) template inline void RU_matrix::_reduce_column(Index target, Index eventIndex) { - auto get_column_with_pivot_ = [&](ID_index pivot) -> Index { - if (pivot == static_cast(-1)) return -1; - if constexpr (Master_matrix::Option_list::has_map_column_container) { - auto it = pivotToColumnIndex_.find(pivot); - if (it == pivotToColumnIndex_.end()) - return -1; - else - return it->second; - } else { - return pivotToColumnIndex_[pivot]; - } - }; - Column& curr = reducedMatrixR_.get_column(target); ID_index pivot = curr.get_pivot(); - Index currIndex = get_column_with_pivot_(pivot); + Index currIndex = _get_column_with_pivot(pivot); - while (pivot != static_cast(-1) && currIndex != static_cast(-1)) { + while (pivot != Master_matrix::template get_null_value() && + currIndex != Master_matrix::template get_null_value()) { _reduce_column_by(target, currIndex); pivot = curr.get_pivot(); - currIndex = get_column_with_pivot_(pivot); + currIndex = _get_column_with_pivot(pivot); } - if (pivot != static_cast(-1)) { + if (pivot != Master_matrix::template get_null_value()) { if constexpr (Master_matrix::Option_list::has_map_column_container) { pivotToColumnIndex_.try_emplace(pivot, target); } else { @@ -846,7 +870,7 @@ inline void RU_matrix::_reduce_column_by(Index target, Index sour Column& curr = reducedMatrixR_.get_column(target); if constexpr (Master_matrix::Option_list::is_z2) { curr += reducedMatrixR_.get_column(source); - // to avoid having to do line operations during vineyards, U is transposed + // to avoid having to do line operations, U is transposed // TODO: explain this somewhere in the documentation... mirrorMatrixU_.get_column(source).push_back(*mirrorMatrixU_.get_column(target).begin()); } else { @@ -856,52 +880,50 @@ inline void RU_matrix::_reduce_column_by(Index target, Index sour operators_->multiply_inplace(coef, operators_->get_characteristic() - curr.get_pivot_value()); curr.multiply_source_and_add(toadd, coef); - // but no transposition for Zp, careful if there will be vineyard or rep cycles in Zp one day + auto entry = *mirrorMatrixU_.get_column(target).begin(); + operators_->multiply_inplace(entry.get_element(), operators_->get_characteristic() - coef); + // to avoid having to do line operations, U is transposed // TODO: explain this somewhere in the documentation... - mirrorMatrixU_.multiply_source_and_add_to(coef, source, target); + mirrorMatrixU_.get_column(source).push_back(entry); } } template -inline void RU_matrix::_update_barcode(ID_index birthPivot, Pos_index death) +inline typename RU_matrix::Index RU_matrix::_get_column_with_pivot(ID_index pivot) const { - if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_vine_update) - Swap_opt::template RU_pairing::_update_barcode(birthPivot, death); - else - Pair_opt::_update_barcode(birthPivot, death); + if (pivot == Master_matrix::template get_null_value()) + return Master_matrix::template get_null_value(); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = pivotToColumnIndex_.find(pivot); + if (it == pivotToColumnIndex_.end()) return Master_matrix::template get_null_value(); + return it->second; + } else { + return pivotToColumnIndex_[pivot]; } } template -inline void RU_matrix::_add_bar(Dimension dim, Pos_index birth) +inline void RU_matrix::_update_barcode(ID_index birthPivot, Pos_index death) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_vine_update) - Swap_opt::template RU_pairing::_add_bar(dim, birth); - else - Pair_opt::_add_bar(dim, birth); + Pair_opt::_update_barcode(birthPivot, death); } } template -inline void RU_matrix::_remove_last_in_barcode(Pos_index eventIndex) +inline void RU_matrix::_add_bar(Dimension dim, Pos_index birth) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_vine_update) - Swap_opt::template RU_pairing::_remove_last(eventIndex); - else - Pair_opt::_remove_last(eventIndex); + Pair_opt::_add_bar(dim, birth); } } template -inline constexpr typename RU_matrix::Bar_dictionary& RU_matrix::_indexToBar() +inline void RU_matrix::_remove_last_in_barcode(Pos_index eventIndex) { - if constexpr (Master_matrix::Option_list::has_vine_update) - return Swap_opt::template RU_pairing::indexToBar_; - else - return Pair_opt::indexToBar_; + if constexpr (Master_matrix::Option_list::has_column_pairings) { + Pair_opt::_remove_last(eventIndex); + } } } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h b/multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h index 84854774..068d73d0 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/allocators/entry_constructors.h @@ -29,31 +29,31 @@ namespace persistence_matrix { * @ingroup persistence_matrix * * @brief @ref Entry factory. Constructs and destroys entry pointers with new and delete. - * + * * @tparam Entry @ref Entry with the right templates. */ template -struct New_entry_constructor -{ +struct New_entry_constructor { /** * @brief Default constructor. */ - New_entry_constructor() {} + New_entry_constructor() = default; /** * @brief Constructs an entry with the given entry arguments. - * + * * @param u Arguments forwarded to the @ref Entry constructor. * @return @ref Entry pointer. */ template - Entry* construct(U&&... u) const { + Entry* construct(U&&... u) const + { return new Entry(std::forward(u)...); } /** * @brief Destroys the given entry. - * + * * @param entry @ref Entry pointer. */ void destroy(Entry* entry) const { delete entry; } @@ -61,7 +61,7 @@ struct New_entry_constructor /** * @brief Swap operator. */ - friend void swap(New_entry_constructor& col1, New_entry_constructor& col2) {} + friend void swap(New_entry_constructor& col1, New_entry_constructor& col2) noexcept {} }; /** @@ -70,67 +70,49 @@ struct New_entry_constructor * * @brief @ref Entry factory. Uses @ref Gudhi::Simple_object_pool, which is based on boost::object_pool, * to construct and destroy entry pointer. - * + * * @tparam Entry @ref Entry with the right templates. */ template -struct Pool_entry_constructor -{ +struct Pool_entry_constructor { public: /** * @brief Default constructor. - * + * */ Pool_entry_constructor() : entryPool_() {} - //TODO: what does happen when the pool is copied? - /** - * @brief Copy constructor. - * - * @param col Factory to copy. - */ - Pool_entry_constructor(const Pool_entry_constructor& col) : entryPool_(col.entryPool_) {} - /** - * @brief Move constructor. - * - * @param col Factory to move. - */ - Pool_entry_constructor(Pool_entry_constructor&& col) : entryPool_(std::move(col.entryPool_)) {} + + // TODO: what does happen when the pool is copied? /** * @brief Constructs an entry with the given entry arguments. - * + * * @param u Arguments forwarded to the @ref Entry constructor. * @return @ref Entry pointer. */ template - Entry* construct(U&&... u) { + Entry* construct(U&&... u) + { return entryPool_.construct(std::forward(u)...); } /** * @brief Destroys the given entry. - * + * * @param entry @ref Entry pointer. */ void destroy(Entry* entry) { entryPool_.destroy(entry); } - //TODO: Again, what does it mean to copy the pool? - /** - * @brief Assign operator. - */ - Pool_entry_constructor& operator=(const Pool_entry_constructor& other) { - entryPool_ = other.entryPool_; - return *this; - } /** * @brief Swap operator. */ - friend void swap(Pool_entry_constructor& col1, Pool_entry_constructor& col2) { + friend void swap(Pool_entry_constructor& col1, Pool_entry_constructor& col2) noexcept + { std::swap(col1.entryPool_, col2.entryPool_); } private: - Simple_object_pool entryPool_; /**< Entry pool. */ + Simple_object_pool entryPool_; /**< Entry pool. */ }; } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h b/multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h index 2fe38e02..f049abe0 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/base_pairing.h @@ -23,7 +23,7 @@ #include #include -#include "boundary_cell_position_to_id_mapper.h" +#include namespace Gudhi { namespace persistence_matrix { @@ -36,7 +36,7 @@ namespace persistence_matrix { * is already managed by the vine update classes. */ struct Dummy_base_pairing { - friend void swap([[maybe_unused]] Dummy_base_pairing& d1, [[maybe_unused]] Dummy_base_pairing& d2) {} + friend void swap([[maybe_unused]] Dummy_base_pairing& d1, [[maybe_unused]] Dummy_base_pairing& d2) noexcept {} }; /** @@ -44,22 +44,30 @@ struct Dummy_base_pairing { * @ingroup persistence_matrix * * @brief Class managing the barcode for @ref Boundary_matrix if the option was enabled. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Base_pairing : public std::conditional< - Master_matrix::Option_list::has_removable_columns, - Cell_position_to_ID_mapper, - Dummy_pos_mapper - >::type +class Base_pairing + : protected std::conditional_t< + Master_matrix::Option_list::has_removable_columns, + Index_mapper >, + Dummy_index_mapper> { + protected: + using Pos_index = typename Master_matrix::Pos_index; + using ID_index = typename Master_matrix::ID_index; + // PIDM = Position to ID Map + using PIDM = std::conditional_t >, + Dummy_index_mapper>; + public: - using Bar = typename Master_matrix::Bar; /**< Bar type. */ - using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ - using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ - using Index = typename Master_matrix::Index; /**< Container index type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ + using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ + using Index = typename Master_matrix::Index; /**< Container index type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Default constructor. @@ -72,7 +80,7 @@ class Base_pairing : public std::conditional< * @warning The barcode will not be recomputed if the matrix is modified later after calling this method * for the first time. So call it only once the matrix is finalized. This behaviour could be changed in the future, * if the need is mentioned. - * + * * @return Const reference to the barcode. */ const Barcode& get_current_barcode(); @@ -80,10 +88,10 @@ class Base_pairing : public std::conditional< /** * @brief Swap operator. */ - friend void swap(Base_pairing& pairing1, Base_pairing& pairing2) { + friend void swap(Base_pairing& pairing1, Base_pairing& pairing2) noexcept + { if constexpr (Master_matrix::Option_list::has_removable_columns) { - swap(static_cast&>(pairing1), - static_cast&>(pairing2)); + swap(static_cast(pairing1), static_cast(pairing2)); } pairing1.barcode_.swap(pairing2.barcode_); pairing1.deathToBar_.swap(pairing2.deathToBar_); @@ -92,50 +100,52 @@ class Base_pairing : public std::conditional< } protected: - using Pos_index = typename Master_matrix::Pos_index; - using ID_index = typename Master_matrix::ID_index; + void _remove_last(Pos_index columnIndex); + void _insert_id_position(ID_index id, Pos_index pos); + void _reset(); + + private: using Dictionary = typename Master_matrix::Bar_dictionary; using Base_matrix = typename Master_matrix::Master_boundary_matrix; - //PIDM = Position to ID Map - using PIDM = typename std::conditional, - Dummy_pos_mapper - >::type; Barcode barcode_; /**< Bar container. */ Dictionary deathToBar_; /**< Map from death index to bar index. */ + bool isReduced_; /**< True if `_reduce()` was called. */ /** * @brief Map from cell ID to cell position. Only stores a pair if ID != position. */ - std::unordered_map idToPosition_; //TODO: test other map types - bool isReduced_; /**< True if `_reduce()` was called. */ + std::unordered_map idToPosition_; // TODO: test other map types void _reduce(); - void _remove_last(Pos_index columnIndex); - //access to inheriting Boundary_matrix class + // access to inheriting Boundary_matrix class constexpr Base_matrix* _matrix() { return static_cast(this); } + constexpr const Base_matrix* _matrix() const { return static_cast(this); } }; template -inline Base_pairing::Base_pairing() : PIDM(), isReduced_(false) +inline Base_pairing::Base_pairing() : PIDM(), isReduced_(false) {} template -inline const typename Base_pairing::Barcode& Base_pairing::get_current_barcode() +inline const typename Base_pairing::Barcode& Base_pairing::get_current_barcode() { if (!isReduced_) _reduce(); return barcode_; } template -inline void Base_pairing::_reduce() +inline void Base_pairing::_reduce() { + constexpr const Pos_index nullDeath = Master_matrix::template get_null_value(); + constexpr const Index nullIndex = Master_matrix::template get_null_value(); + std::unordered_map negativeColumns(_matrix()->get_number_of_columns()); auto dim = _matrix()->get_max_dimension(); std::vector > columnsByDim(dim + 1); + for (auto& v : columnsByDim) v.reserve(_matrix()->get_number_of_columns()); for (unsigned int i = 0; i < _matrix()->get_number_of_columns(); i++) { columnsByDim[dim - _matrix()->get_column_dimension(i)].push_back(i); } @@ -145,16 +155,17 @@ inline void Base_pairing::_reduce() auto& curr = _matrix()->get_column(i); if (curr.is_empty()) { if (negativeColumns.find(i) == negativeColumns.end()) { - barcode_.emplace_back(i, -1, dim); + barcode_.emplace_back(i, nullDeath, dim); } } else { ID_index pivot = curr.get_pivot(); auto it = idToPosition_.find(pivot); Index pivotColumnNumber = it == idToPosition_.end() ? pivot : it->second; auto itNeg = negativeColumns.find(pivotColumnNumber); - Index pivotKiller = itNeg == negativeColumns.end() ? -1 : itNeg->second; + Index pivotKiller = itNeg == negativeColumns.end() ? nullIndex : itNeg->second; - while (pivot != static_cast(-1) && pivotKiller != static_cast(-1)) { + while (pivot != Master_matrix::template get_null_value() && + pivotKiller != Master_matrix::template get_null_value()) { if constexpr (Master_matrix::Option_list::is_z2) { curr += _matrix()->get_column(pivotKiller); } else { @@ -170,16 +181,16 @@ inline void Base_pairing::_reduce() it = idToPosition_.find(pivot); pivotColumnNumber = it == idToPosition_.end() ? pivot : it->second; itNeg = negativeColumns.find(pivotColumnNumber); - pivotKiller = itNeg == negativeColumns.end() ? -1 : itNeg->second; + pivotKiller = itNeg == negativeColumns.end() ? nullIndex : itNeg->second; } - if (pivot != static_cast(-1)) { + if (pivot != Master_matrix::template get_null_value()) { negativeColumns.emplace(pivotColumnNumber, i); _matrix()->get_column(pivotColumnNumber).clear(); barcode_.emplace_back(pivotColumnNumber, i, dim - 1); } else { curr.clear(); - barcode_.emplace_back(i, -1, dim); + barcode_.emplace_back(i, nullDeath, dim); } } } @@ -192,7 +203,7 @@ inline void Base_pairing::_reduce() // map can only be constructed once barcode is sorted for (Index i = 0; i < barcode_.size(); ++i) { auto d = barcode_[i].death; - if (d != static_cast(-1)) { + if (d != Master_matrix::template get_null_value()) { deathToBar_.emplace(d, i); } } @@ -202,7 +213,7 @@ inline void Base_pairing::_reduce() } template -inline void Base_pairing::_remove_last(Pos_index columnIndex) +inline void Base_pairing::_remove_last(Pos_index columnIndex) { static_assert(Master_matrix::Option_list::has_removable_columns, "remove_last not available."); @@ -212,18 +223,33 @@ inline void Base_pairing::_remove_last(Pos_index columnIndex) if (it == deathToBar_.end()) { // birth barcode_.pop_back(); // sorted by birth and columnIndex has to be the highest one } else { // death - barcode_[it->second].death = -1; + barcode_[it->second].death = Master_matrix::template get_null_value(); deathToBar_.erase(it); }; } auto it = PIDM::map_.find(columnIndex); - if (it != PIDM::map_.end()){ + if (it != PIDM::map_.end()) { idToPosition_.erase(it->second); PIDM::map_.erase(it); } } +template +inline void Base_pairing::_insert_id_position(ID_index id, Pos_index pos) +{ + idToPosition_.emplace(id, pos); +} + +template +inline void Base_pairing::_reset() +{ + barcode_.clear(); + deathToBar_.clear(); + isReduced_ = false; + idToPosition_.clear(); +} + } // namespace persistence_matrix } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Persistence_matrix/base_swap.h b/multipers/gudhi/gudhi/Persistence_matrix/base_swap.h index db6af1cc..44a3f525 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/base_swap.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/base_swap.h @@ -31,7 +31,7 @@ namespace persistence_matrix { * Inherited instead of @ref Base_swap, when the column and row swaps are not enabled. */ struct Dummy_base_swap { - friend void swap([[maybe_unused]] Dummy_base_swap& d1, [[maybe_unused]] Dummy_base_swap& d2) {} + friend void swap([[maybe_unused]] Dummy_base_swap& d1, [[maybe_unused]] Dummy_base_swap& d2) noexcept {} Dummy_base_swap([[maybe_unused]] unsigned int numberOfColumns = 0) {} }; @@ -41,16 +41,17 @@ struct Dummy_base_swap { * @ingroup persistence_matrix * * @brief Class managing the column and row swaps in @ref Base_matrix and @ref Boundary_matrix. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. * @tparam Base_matrix Either @ref Base_matrix or @ref Boundary_matrix. */ template -class Base_swap { +class Base_swap +{ public: - using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ - using Index = typename Master_matrix::Index; /**< Container index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ + using Index = typename Master_matrix::Index; /**< Container index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ /** * @brief Default constructor. @@ -58,27 +59,29 @@ class Base_swap { Base_swap(); /** * @brief As default constructor, but reserves spaces for @p numberOfColumns columns. - * + * * @param numberOfColumns Number of columns to reserve space for. */ Base_swap(unsigned int numberOfColumns); /** * @brief Copy constructor. - * + * * @param matrixToCopy Matrix to copy. */ Base_swap(const Base_swap& matrixToCopy) = default; /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Base_swap(Base_swap&& other) noexcept; + ~Base_swap() = default; + /** * @brief Swaps the two columns at given indices in the column container. Does not updates the column index value, * potentially stored in the entries. This will be done when calling `_orderRows()`. - * + * * @param columnIndex1 First @ref MatIdx column index. * @param columnIndex2 Second @ref MatIdx column index. */ @@ -86,7 +89,7 @@ class Base_swap { /** * @brief Swaps the two rows at the given indices, but in a lazy manner. That is, the swap is registered but not * executed. The reordering will be done when calling `_orderRows()`. - * + * * @param rowIndex1 First row index. * @param rowIndex2 Second row index. */ @@ -95,17 +98,31 @@ class Base_swap { /** * @brief Assign operator. */ - Base_swap& operator=(Base_swap other); + Base_swap& operator=(const Base_swap& other) = default; + /** + * @brief Move assign operator. + */ + Base_swap& operator=(Base_swap&& other) noexcept; + /** * @brief Swap operator. */ - friend void swap(Base_swap& base1, Base_swap& base2) { + friend void swap(Base_swap& base1, Base_swap& base2) noexcept + { base1.indexToRow_.swap(base2.indexToRow_); base1.rowToIndex_.swap(base2.rowToIndex_); std::swap(base1.rowSwapped_, base2.rowSwapped_); } protected: + void _orderRows(); + void _initialize_row_index(Index index); + Index _erase_row(Index index); + Index _get_row_index(Index index) const; + bool _row_were_swapped() const; + void _reset(); + + private: using Index_dictionary = typename Master_matrix::template Dictionary; using Row_dictionary = typename Master_matrix::template Dictionary; @@ -113,16 +130,16 @@ class Base_swap { Row_dictionary rowToIndex_; /**< Map from index in row container to "public" row index. */ bool rowSwapped_; /**< True if any rows were swapped since last call to `_orderRows()`. */ - void _orderRows(); - - //access to inheriting matrix class + // access to inheriting matrix class constexpr Base_matrix* _matrix() { return static_cast(this); } + constexpr const Base_matrix* _matrix() const { return static_cast(this); } }; template inline Base_swap::Base_swap() : rowSwapped_(false) -{} +{ +} template inline Base_swap::Base_swap(unsigned int numberOfColumns) @@ -138,14 +155,15 @@ template inline Base_swap::Base_swap(Base_swap&& other) noexcept : indexToRow_(std::move(other.indexToRow_)), rowToIndex_(std::move(other.rowToIndex_)), - rowSwapped_(std::exchange(other.rowSwapped_, 0)) -{} + rowSwapped_(std::exchange(other.rowSwapped_, false)) +{ +} template inline void Base_swap::swap_columns(Index columnIndex1, Index columnIndex2) { swap(_matrix()->matrix_.at(columnIndex1), _matrix()->matrix_.at(columnIndex2)); - if constexpr (Master_matrix::Option_list::has_row_access) rowSwapped_ = true; //to update column index in entries. + if constexpr (Master_matrix::Option_list::has_row_access) rowSwapped_ = true; // to update column index in entries. } template @@ -184,11 +202,12 @@ inline void Base_swap::swap_rows(ID_index rowIndex1, } template -inline Base_swap& Base_swap::operator=(Base_swap other) +inline Base_swap& Base_swap::operator=( + Base_swap&& other) noexcept { - indexToRow_.swap(other.indexToRow_); - rowToIndex_.swap(other.rowToIndex_); - std::swap(rowSwapped_, other.rowSwapped_); + indexToRow_ = std::move(other.indexToRow_); + rowToIndex_ = std::move(other.rowToIndex_); + rowSwapped_ = std::exchange(other.rowSwapped_, false); return *this; } @@ -205,6 +224,62 @@ inline void Base_swap::_orderRows() rowSwapped_ = false; } +template +inline void Base_swap::_initialize_row_index(Index index) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + indexToRow_.emplace(index, index); + rowToIndex_.emplace(index, index); + } else { + indexToRow_.reserve(index + 1); + rowToIndex_.reserve(index + 1); + for (Index i = indexToRow_.size(); i <= index; ++i) { + indexToRow_.push_back(i); + rowToIndex_.push_back(i); + } + } +} + +template +inline typename Base_swap::Index Base_swap::_erase_row( + Index index) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = indexToRow_.find(index); + auto rowID = it->second; + rowToIndex_.erase(rowID); + indexToRow_.erase(it); + return rowID; + } else { + return indexToRow_[index]; + } +} + +template +inline typename Base_swap::Index Base_swap::_get_row_index( + Index index) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return indexToRow_.at(index); + } else { + return indexToRow_[index]; + } +} + +template +inline bool Base_swap::_row_were_swapped() const +{ + return rowSwapped_; +} + +template +inline void Base_swap::_reset() +{ + indexToRow_.clear(); + rowToIndex_.clear(); + rowSwapped_ = false; +} + } // namespace persistence_matrix } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h b/multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h deleted file mode 100644 index 1b85b07c..00000000 --- a/multipers/gudhi/gudhi/Persistence_matrix/boundary_cell_position_to_id_mapper.h +++ /dev/null @@ -1,60 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022-24 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -/** - * @file boundary_cell_position_to_id_mapper.h - * @author Hannah Schreiber - * @brief Contains the @ref Gudhi::persistence_matrix::Cell_position_to_ID_mapper class and - * @ref Gudhi::persistence_matrix::Dummy_pos_mapper structure. - */ - -#ifndef PM_ID_POS_MAPPER_H -#define PM_ID_POS_MAPPER_H - -#include - -namespace Gudhi { -namespace persistence_matrix { - -/** - * @private - * @ingroup persistence_matrix - * - * @brief Empty structure. - * Inherited instead of @ref Cell_position_to_ID_mapper. - */ -struct Dummy_pos_mapper { - friend void swap([[maybe_unused]] Dummy_pos_mapper& d1, [[maybe_unused]] Dummy_pos_mapper& d2) {} -}; - -/** - * @private - * @ingroup persistence_matrix - * - * @brief Map from cell position to cell ID. Only stores a pair if ID != position and has_removable_column is true. - * - * @tparam ID_index @ref IDIdx index type - * @tparam Pos_index @ref PosIdx index type - */ -template -struct Cell_position_to_ID_mapper { - using Index_map = std::unordered_map; //TODO: test other map types - - Index_map map_; - - friend void swap(Cell_position_to_ID_mapper& mapper1, Cell_position_to_ID_mapper& mapper2) { - mapper1.map_.swap(mapper2.map_); - } -}; - -} // namespace persistence_matrix -} // namespace Gudhi - -#endif // PM_ID_POS_MAPPER_H diff --git a/multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h b/multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h deleted file mode 100644 index 54648041..00000000 --- a/multipers/gudhi/gudhi/Persistence_matrix/boundary_face_position_to_id_mapper.h +++ /dev/null @@ -1,60 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022-24 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -/** - * @file base_pairing.h - * @author Hannah Schreiber - * @brief Contains the @ref Gudhi::persistence_matrix::Base_pairing class and - * @ref Gudhi::persistence_matrix::Dummy_base_pairing structure. - */ - -#ifndef PM_ID_POS_MAPPER_H -#define PM_ID_POS_MAPPER_H - -#include - -namespace Gudhi { -namespace persistence_matrix { - -/** - * @private - * @ingroup persistence_matrix - * - * @brief Empty structure. - * Inherited instead of @ref Face_position_to_ID_mapper. - */ -struct Dummy_pos_mapper { - friend void swap([[maybe_unused]] Dummy_pos_mapper& d1, [[maybe_unused]] Dummy_pos_mapper& d2) {} -}; - -/** - * @private - * @ingroup persistence_matrix - * - * @brief Map from face position to face ID. Only stores a pair if ID != position and has_removable_column is true. - * - * @tparam ID_index @ref IDIdx index type - * @tparam Pos_index @ref PosIdx index type - */ -template -struct Face_position_to_ID_mapper { - using Index_map = std::unordered_map; //TODO: test other map types - - Index_map map_; - - friend void swap(Face_position_to_ID_mapper& mapper1, Face_position_to_ID_mapper& mapper2) { - mapper1.map_.swap(mapper2.map_); - } -}; - -} // namespace persistence_matrix -} // namespace Gudhi - -#endif // PM_ID_POS_MAPPER_H diff --git a/multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h b/multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h index 46a71a76..470cce2a 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/chain_pairing.h @@ -18,11 +18,12 @@ #ifndef PM_CHAIN_PAIRING_H #define PM_CHAIN_PAIRING_H -#include //std::move - namespace Gudhi { namespace persistence_matrix { +template +class Chain_barcode_swap; + /** * @ingroup persistence_matrix * @@ -30,9 +31,8 @@ namespace persistence_matrix { * Inherited instead of @ref Chain_pairing, when the computation of the barcode was not enabled or if the pairing * is already managed by the vine update classes. */ -struct Dummy_chain_pairing -{ - friend void swap([[maybe_unused]] Dummy_chain_pairing& d1, [[maybe_unused]] Dummy_chain_pairing& d2) {} +struct Dummy_chain_pairing { + friend void swap([[maybe_unused]] Dummy_chain_pairing& d1, [[maybe_unused]] Dummy_chain_pairing& d2) noexcept {} }; /** @@ -40,94 +40,128 @@ struct Dummy_chain_pairing * @ingroup persistence_matrix * * @brief Class managing the barcode for @ref Chain_matrix if the option was enabled. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Chain_pairing +class Chain_pairing { public: - using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ + using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** * @brief Default constructor. */ - Chain_pairing(); - /** - * @brief Copy constructor. - * - * @param matrixToCopy Matrix to copy. - */ - Chain_pairing(const Chain_pairing& matrixToCopy); - /** - * @brief Move constructor. - * - * @param other Matrix to move. - */ - Chain_pairing(Chain_pairing&& other) noexcept; + Chain_pairing() = default; /** * @brief Returns the current barcode which is maintained at any insertion, removal or vine swap. - * + * * @return Const reference to the barcode. */ const Barcode& get_current_barcode() const; - /** - * @brief Assign operator. - */ - Chain_pairing& operator=(Chain_pairing other); /** * @brief Swap operator. */ - friend void swap(Chain_pairing& pairing1, Chain_pairing& pairing2) { + friend void swap(Chain_pairing& pairing1, Chain_pairing& pairing2) noexcept + { pairing1.barcode_.swap(pairing2.barcode_); pairing1.indexToBar_.swap(pairing2.indexToBar_); - std::swap(pairing1.nextPosition_, pairing2.nextPosition_); } protected: - using Dictionary = typename Master_matrix::Bar_dictionary; using Pos_index = typename Master_matrix::Pos_index; + using Index = typename Master_matrix::Index; + + void _update_barcode(Pos_index birth, Pos_index death); + void _add_bar(Dimension dim, Pos_index birth); + void _erase_bar(Pos_index event); + Pos_index _death(Index index) const; + Pos_index _birth(Index index) const; + void _reset(); + + private: + using Dictionary = typename Master_matrix::Bar_dictionary; + + // could also just mark everything as protected as Chain_barcode_swap inherits from Chain_pairing + // but this way, it marks a better difference between "class using this mixin" with "class extending this mixin" + friend Chain_barcode_swap; - Barcode barcode_; /**< Bar container. */ - Dictionary indexToBar_; /**< Map from @ref MatIdx index to bar index. */ - Pos_index nextPosition_; /**< Next relative position in the filtration. */ + Barcode barcode_; /**< Bar container. */ + Dictionary indexToBar_; /**< Map from @ref MatIdx index to bar index. */ }; template -inline Chain_pairing::Chain_pairing() : nextPosition_(0) -{} +inline const typename Chain_pairing::Barcode& Chain_pairing::get_current_barcode() const +{ + return barcode_; +} template -inline Chain_pairing::Chain_pairing(const Chain_pairing& matrixToCopy) - : barcode_(matrixToCopy.barcode_), - indexToBar_(matrixToCopy.indexToBar_), - nextPosition_(matrixToCopy.nextPosition_) -{} +inline void Chain_pairing::_update_barcode(Pos_index birth, Pos_index death) +{ + if constexpr (Master_matrix::Option_list::has_removable_columns) { + auto& barIt = indexToBar_.at(birth); + barIt->death = death; + indexToBar_.try_emplace(death, barIt); // list so iterators are stable + } else { + barcode_[indexToBar_[birth]].death = death; + indexToBar_.push_back(indexToBar_[birth]); + } +} template -inline Chain_pairing::Chain_pairing(Chain_pairing&& other) noexcept - : barcode_(std::move(other.barcode_)), - indexToBar_(std::move(other.indexToBar_)), - nextPosition_(std::exchange(other.nextPosition_, 0)) -{} +inline void Chain_pairing::_add_bar(Dimension dim, Pos_index birth) +{ + barcode_.emplace_back(birth, Master_matrix::template get_null_value(), dim); + if constexpr (Master_matrix::Option_list::has_removable_columns) { + indexToBar_.try_emplace(birth, --barcode_.end()); + } else { + indexToBar_.push_back(barcode_.size() - 1); + } +} template -inline const typename Chain_pairing::Barcode& Chain_pairing::get_current_barcode() - const +inline void Chain_pairing::_erase_bar(Pos_index event) { - return barcode_; + auto it = indexToBar_.find(event); + typename Barcode::iterator bar = it->second; + + if (bar->death == Master_matrix::template get_null_value()) + barcode_.erase(bar); + else + bar->death = Master_matrix::template get_null_value(); + + indexToBar_.erase(it); +} + +template +inline typename Chain_pairing::Pos_index Chain_pairing::_death(Index index) const +{ + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return indexToBar_.at(index)->death; + } else { + return barcode_.at(indexToBar_.at(index)).death; + } +} + +template +inline typename Chain_pairing::Pos_index Chain_pairing::_birth(Index index) const +{ + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return indexToBar_.at(index)->birth; + } else { + return barcode_.at(indexToBar_.at(index)).birth; + } } template -inline Chain_pairing& Chain_pairing::operator=(Chain_pairing other) +inline void Chain_pairing::_reset() { - barcode_.swap(other.barcode_); - indexToBar_.swap(other.indexToBar_); - std::swap(nextPosition_, other.nextPosition_); - return *this; + barcode_.clear(); + indexToBar_.clear(); } } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h b/multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h index 04dde05b..a160018b 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/chain_rep_cycles.h @@ -22,6 +22,8 @@ #include //std::sort #include +#include + namespace Gudhi { namespace persistence_matrix { @@ -32,45 +34,34 @@ namespace persistence_matrix { * Inherited instead of @ref Chain_representative_cycles, when the computation of the representative cycles * were not enabled. */ -struct Dummy_chain_representative_cycles -{ +struct Dummy_chain_representative_cycles { friend void swap([[maybe_unused]] Dummy_chain_representative_cycles& d1, - [[maybe_unused]] Dummy_chain_representative_cycles& d2) {} + [[maybe_unused]] Dummy_chain_representative_cycles& d2) noexcept + {} }; -// TODO: add coefficients ? Only Z2 token into account for now. /** * @class Chain_representative_cycles chain_rep_cycles.h gudhi/Persistence_matrix/chain_rep_cycles.h * @ingroup persistence_matrix * * @brief Class managing the representative cycles for @ref Chain_matrix if the option was enabled. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Chain_representative_cycles +class Chain_representative_cycles { public: - using Bar = typename Master_matrix::Bar; /**< Bar type. */ - using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ - using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ + using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ /** * @brief Default constructor. */ - Chain_representative_cycles(); - /** - * @brief Copy constructor. - * - * @param matrixToCopy Matrix to copy. - */ - Chain_representative_cycles(const Chain_representative_cycles& matrixToCopy); - /** - * @brief Move constructor. - * - * @param other Matrix to move. - */ - Chain_representative_cycles(Chain_representative_cycles&& other) noexcept; + Chain_representative_cycles() = default; /** * @brief Computes the current representative cycles of the matrix. @@ -80,7 +71,7 @@ class Chain_representative_cycles /** * @brief Returns the current representative cycles. If the matrix is modified later after the first call, * @ref update_representative_cycles has to be called to update the returned cycles. - * + * * @return A const reference to a vector of @ref Matrix::Cycle containing all representative cycles. */ const std::vector& get_representative_cycles(); @@ -88,80 +79,90 @@ class Chain_representative_cycles * @brief Returns the representative cycle corresponding to the given bar. * If the matrix is modified later after the first call, * @ref update_representative_cycles has to be called to update the returned cycles. - * + * * @param bar Bar corresponding to the wanted representative cycle. * @return A const reference to the representative cycle. */ const Cycle& get_representative_cycle(const Bar& bar); - /** - * @brief Assign operator. - */ - Chain_representative_cycles& operator=(Chain_representative_cycles other); /** * @brief Swap operator. */ - friend void swap(Chain_representative_cycles& base1, Chain_representative_cycles& base2) { + friend void swap(Chain_representative_cycles& base1, Chain_representative_cycles& base2) noexcept + { base1.representativeCycles_.swap(base2.representativeCycles_); base1.birthToCycle_.swap(base2.birthToCycle_); } + protected: + void _reset(); + private: using Master_chain_matrix = typename Master_matrix::Master_chain_matrix; - std::vector representativeCycles_; /**< Cycle container. */ - std::vector birthToCycle_; /**< Map from birth index to cycle index. */ + std::vector representativeCycles_; /**< Cycle container. */ + std::vector birthToCycle_; /**< Map from birth index to cycle index. */ - //access to inheriting Chain_matrix class + // access to inheriting Chain_matrix class constexpr Master_chain_matrix* _matrix() { return static_cast(this); } + constexpr const Master_chain_matrix* _matrix() const { return static_cast(this); } }; -template -inline Chain_representative_cycles::Chain_representative_cycles() -{} - -template -inline Chain_representative_cycles::Chain_representative_cycles( - const Chain_representative_cycles& matrixToCopy) - : representativeCycles_(matrixToCopy.representativeCycles_), birthToCycle_(matrixToCopy.birthToCycle_) -{} - -template -inline Chain_representative_cycles::Chain_representative_cycles( - Chain_representative_cycles&& other) noexcept - : representativeCycles_(std::move(other.representativeCycles_)), birthToCycle_(std::move(other.birthToCycle_)) -{} - template inline void Chain_representative_cycles::update_representative_cycles() { + auto nberColumns = _matrix()->get_number_of_columns(); + auto get_position = [&](ID_index pivot) { + if constexpr (Master_matrix::Option_list::has_vine_update) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return _matrix()->map_.at(pivot); + } else { + return _matrix()->map_[pivot]; + } + } else { + return pivot; + } + }; + birthToCycle_.clear(); - birthToCycle_.resize(_matrix()->get_number_of_columns(), -1); + birthToCycle_.resize(nberColumns, Master_matrix::template get_null_value()); representativeCycles_.clear(); - // for birthToCycle_, assumes that @ref PosIdx == @ref IDIdx, ie pivot == birth index... which is not true with - // vineyards - // TODO: with vineyard, there is a @ref IDIdx --> @ref PosIdx map stored. somehow get access to it here - for (typename Master_matrix::ID_index i = 0; i < _matrix()->get_number_of_columns(); i++) { + for (ID_index i = 0; i < nberColumns; i++) { auto& col = _matrix()->get_column(_matrix()->get_column_with_pivot(i)); - if (!col.is_paired() || i < col.get_paired_chain_index()) { + if (!col.is_paired() || get_position(i) < get_position(_matrix()->get_pivot(col.get_paired_chain_index()))) { Cycle cycle; - for (auto& c : col) { - cycle.push_back(c.get_row_index()); + if constexpr (is_well_behaved::value) { + cycle.reserve(col.size()); + for (const auto& c : col) { + if constexpr (Master_matrix::Option_list::is_z2) { + cycle.push_back(c.get_row_index()); + } else { + cycle.push_back({c.get_row_index(), c.get_element()}); + } + } + } else { + auto cont = col.get_content(); + for (Index j = 0; j < cont.size(); ++j) { + if (cont[j] != 0) { + if constexpr (Master_matrix::Option_list::is_z2) { + cycle.push_back(j); + } else { + cycle.push_back({j, cont[j]}); + } + } + } } - if constexpr (std::is_same_v || - std::is_same_v) - std::sort(cycle.begin(), cycle.end()); - representativeCycles_.push_back(cycle); - birthToCycle_[i] = representativeCycles_.size() - 1; + representativeCycles_.push_back(std::move(cycle)); + birthToCycle_[get_position(i)] = representativeCycles_.size() - 1; } } } template inline const std::vector::Cycle>& -Chain_representative_cycles::get_representative_cycles() +Chain_representative_cycles::get_representative_cycles() { if (representativeCycles_.empty()) update_representative_cycles(); return representativeCycles_; @@ -169,19 +170,17 @@ Chain_representative_cycles::get_representative_cycles() template inline const typename Chain_representative_cycles::Cycle& -Chain_representative_cycles::get_representative_cycle(const Bar& bar) +Chain_representative_cycles::get_representative_cycle(const Bar& bar) { if (representativeCycles_.empty()) update_representative_cycles(); return representativeCycles_[birthToCycle_[bar.birth]]; } template -inline Chain_representative_cycles& Chain_representative_cycles::operator=( - Chain_representative_cycles other) +inline void Chain_representative_cycles::_reset() { - representativeCycles_.swap(other.representativeCycles_); - birthToCycle_.swap(other.birthToCycle_); - return *this; + representativeCycles_.clear(); + birthToCycle_.clear(); } } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h b/multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h index 3168b540..298216d3 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/chain_vine_swap.h @@ -20,12 +20,12 @@ #ifndef PM_CHAIN_VINE_SWAP_H #define PM_CHAIN_VINE_SWAP_H -#include //std::swap & std::move #include -#include //std::function -#include //std::invalid_argument +#include //std::swap & std::move +#include //std::function +#include //std::invalid_argument -#include "chain_pairing.h" +#include namespace Gudhi { namespace persistence_matrix { @@ -35,13 +35,13 @@ namespace persistence_matrix { * * @brief Default death comparator. Simply assumes that two positive paired columns are never swapped. Which is true * for the use case in zigzag persistence for example. - * + * * @param columnIndex1 First column index. * @param columnIndex2 Second column index. - * @return false + * @return false */ constexpr bool _no_G_death_comparator([[maybe_unused]] unsigned int columnIndex1, - [[maybe_unused]] unsigned int columnIndex2) + [[maybe_unused]] unsigned int columnIndex2) { return false; } @@ -52,14 +52,17 @@ constexpr bool _no_G_death_comparator([[maybe_unused]] unsigned int columnIndex1 * @brief Empty structure. * Inherited instead of @ref Chain_vine_swap, when vine swaps are not enabled. */ -struct Dummy_chain_vine_swap -{ - friend void swap([[maybe_unused]] Dummy_chain_vine_swap& d1, [[maybe_unused]] Dummy_chain_vine_swap& d2) {} +struct Dummy_chain_vine_swap { + using CP = void; + + friend void swap([[maybe_unused]] Dummy_chain_vine_swap& d1, [[maybe_unused]] Dummy_chain_vine_swap& d2) noexcept {} + + Dummy_chain_vine_swap() = default; - Dummy_chain_vine_swap() {} template Dummy_chain_vine_swap([[maybe_unused]] const BirthComparatorFunction& birthComparator, - [[maybe_unused]] const DeathComparatorFunction& deathComparator) {} + [[maybe_unused]] const DeathComparatorFunction& deathComparator) + {} }; /** @@ -68,65 +71,73 @@ struct Dummy_chain_vine_swap * @brief Empty structure. * Inherited instead of @ref Chain_barcode_swap, when the barcode is not stored. */ -struct Dummy_chain_vine_pairing -{ - friend void swap([[maybe_unused]] Dummy_chain_vine_pairing& d1, [[maybe_unused]] Dummy_chain_vine_pairing& d2) {} +struct Dummy_chain_vine_pairing { + friend void swap([[maybe_unused]] Dummy_chain_vine_pairing& d1, + [[maybe_unused]] Dummy_chain_vine_pairing& d2) noexcept + {} }; /** * @ingroup persistence_matrix * * @brief Class managing the barcode for @ref Chain_vine_swap. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Chain_barcode_swap : public Chain_pairing +class Chain_barcode_swap : public Chain_pairing { public: - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - //CP = Chain Pairing + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + // CP = Chain Pairing using CP = Chain_pairing; /** * @brief Default constructor. */ - Chain_barcode_swap(){}; + Chain_barcode_swap() = default; /** * @brief Copy constructor. - * + * * @param toCopy Matrix to copy. */ - Chain_barcode_swap(const Chain_barcode_swap& toCopy) - : CP(static_cast(toCopy)), pivotToPosition_(toCopy.pivotToPosition_){}; + Chain_barcode_swap(const Chain_barcode_swap& toCopy) : CP(static_cast(toCopy)) {}; /** * @brief Move constructor. - * + * * @param other Matrix to move. */ - Chain_barcode_swap(Chain_barcode_swap&& other) - : CP(std::move(static_cast(other))), pivotToPosition_(std::move(other.pivotToPosition_)){}; + Chain_barcode_swap(Chain_barcode_swap&& other) noexcept : CP(std::move(static_cast(other))) {}; - protected: - using Dictionary = typename Master_matrix::template Dictionary; + ~Chain_barcode_swap() = default; - Dictionary pivotToPosition_; // necessary to keep track of the barcode changes + Chain_barcode_swap& operator=(Chain_barcode_swap other) + { + CP::operator=(other); + return *this; + } - void swap_positions(ID_index pivot1, ID_index pivot2) { - if constexpr (Master_matrix::Option_list::has_map_column_container) { - std::swap(pivotToPosition_.at(pivot1), pivotToPosition_.at(pivot2)); - } else { - std::swap(pivotToPosition_[pivot1], pivotToPosition_[pivot2]); - } + Chain_barcode_swap& operator=(Chain_barcode_swap&& other) noexcept + { + CP::operator=(std::move(other)); + return *this; } - bool is_negative_in_pair(ID_index pivot) const { + friend void swap(Chain_barcode_swap& swap1, Chain_barcode_swap& swap2) noexcept + { + swap(static_cast(swap1), static_cast(swap2)); + } + + protected: + bool _is_negative_in_bar(ID_index pivot) const + { Pos_index pos = _get_pivot_position(pivot); - return death(pivot) == pos; + return _death_val(pivot) == pos; } - void positive_transpose(ID_index pivot1, ID_index pivot2) { + void _positive_transpose_barcode(ID_index pivot1, ID_index pivot2) + { Pos_index pos1 = _get_pivot_position(pivot1); Pos_index pos2 = _get_pivot_position(pivot2); @@ -135,7 +146,8 @@ class Chain_barcode_swap : public Chain_pairing std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); } - void negative_transpose(ID_index pivot1, ID_index pivot2) { + void _negative_transpose_barcode(ID_index pivot1, ID_index pivot2) + { Pos_index pos1 = _get_pivot_position(pivot1); Pos_index pos2 = _get_pivot_position(pivot2); @@ -144,7 +156,8 @@ class Chain_barcode_swap : public Chain_pairing std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); } - void positive_negative_transpose(ID_index pivot1, ID_index pivot2) { + void _positive_negative_transpose_barcode(ID_index pivot1, ID_index pivot2) + { Pos_index pos1 = _get_pivot_position(pivot1); Pos_index pos2 = _get_pivot_position(pivot2); @@ -153,7 +166,8 @@ class Chain_barcode_swap : public Chain_pairing std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); } - void negative_positive_transpose(ID_index pivot1, ID_index pivot2) { + void _negative_positive_transpose_barcode(ID_index pivot1, ID_index pivot2) + { Pos_index pos1 = _get_pivot_position(pivot1); Pos_index pos2 = _get_pivot_position(pivot2); @@ -162,64 +176,48 @@ class Chain_barcode_swap : public Chain_pairing std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); } - bool are_adjacent(ID_index pivot1, ID_index pivot2) const { + bool _are_adjacent(ID_index pivot1, ID_index pivot2) const + { Pos_index pos1 = _get_pivot_position(pivot1); Pos_index pos2 = _get_pivot_position(pivot2); return pos1 < pos2 ? (pos2 - pos1) == 1 : (pos1 - pos2) == 1; } - Chain_barcode_swap& operator=(Chain_barcode_swap other) { - Chain_pairing::operator=(other); - pivotToPosition_.swap(other.pivotToPosition_); - } - friend void swap(Chain_barcode_swap& swap1, Chain_barcode_swap& swap2) { - swap(static_cast&>(swap1), static_cast&>(swap2)); - swap1.pivotToPosition_.swap(swap2.pivotToPosition_); - } - - Pos_index death(ID_index pivot) const { - Pos_index simplexIndex = _get_pivot_position(pivot); + Pos_index _death_val(ID_index pivot) const { return CP::_death(_get_pivot_position(pivot)); } - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return CP::indexToBar_.at(simplexIndex)->death; - } else { - return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).death; - } - } + Pos_index _birth_val(ID_index pivot) const { return CP::_birth(_get_pivot_position(pivot)); } - Pos_index birth(ID_index pivot) const { - Pos_index simplexIndex = _get_pivot_position(pivot); - - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return CP::indexToBar_.at(simplexIndex)->birth; - } else { - return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).birth; - } - } + void _reset() { CP::_reset(); } private: - Pos_index _get_pivot_position(ID_index pivot) const { + using Master_chain_matrix = typename Master_matrix::Master_chain_matrix; + + Pos_index _get_pivot_position(ID_index pivot) const + { + const auto& map = static_cast(this)->map_; if constexpr (Master_matrix::Option_list::has_map_column_container) { - return pivotToPosition_.at( - pivot); // quite often called, make public and pass position instead of pivot to avoid find() every time? + // TODO: quite often called, make public and pass position instead of pivot to avoid find() every time? + return map.at(pivot); } else { - return pivotToPosition_[pivot]; + return map[pivot]; } } - Pos_index& _death(Pos_index simplexIndex) { + Pos_index& _death(Pos_index index) + { if constexpr (Master_matrix::Option_list::has_removable_columns) { - return CP::indexToBar_.at(simplexIndex)->death; + return CP::indexToBar_.at(index)->death; } else { - return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).death; + return CP::barcode_.at(CP::indexToBar_.at(index)).death; } } - Pos_index& _birth(Pos_index simplexIndex) { + Pos_index& _birth(Pos_index index) + { if constexpr (Master_matrix::Option_list::has_removable_columns) { - return CP::indexToBar_.at(simplexIndex)->birth; + return CP::indexToBar_.at(index)->birth; } else { - return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).birth; + return CP::barcode_.at(CP::indexToBar_.at(index)).birth; } } }; @@ -229,22 +227,19 @@ class Chain_barcode_swap : public Chain_pairing * @ingroup persistence_matrix * * @brief Class managing the vine swaps for @ref Chain_matrix. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Chain_vine_swap : public std::conditional, - Dummy_chain_vine_pairing - >::type +class Chain_vine_swap { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ - using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ - using Column = typename Master_matrix::Column; /**< Column type. */ - typedef bool (*EventCompFuncPointer)(Pos_index, Pos_index); /**< Pointer type for birth/death comparators. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Column_container = typename Master_matrix::Column_container; /**< Column container type. */ + using Column = typename Master_matrix::Column; /**< Column type. */ + using EventCompFuncPointer = bool (*)(Pos_index, Pos_index); /**< Pointer type for birth/death comparators. */ /** * @brief Default constructor. Only available if @ref PersistenceMatrixOptions::has_column_pairings is true. @@ -252,7 +247,7 @@ class Chain_vine_swap : public std::conditional birthComparator, - std::function deathComparator = _no_G_death_comparator); - /** - * @brief Copy constructor. - * - * @param matrixToCopy Matrix to copy. - */ - Chain_vine_swap(const Chain_vine_swap& matrixToCopy); - /** - * @brief Move constructor. - * - * @param other Matrix to move. - */ - Chain_vine_swap(Chain_vine_swap&& other) noexcept; + Chain_vine_swap(std::function birthComparator, + std::function deathComparator = _no_G_death_comparator); /** * @brief Does the same than @ref vine_swap, but assumes that the swap is non trivial and * therefore skips a part of the case study. - * + * * @param columnIndex1 @ref MatIdx index of the first cell. * @param columnIndex2 @ref MatIdx index of the second cell. * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of @@ -295,7 +278,7 @@ class Chain_vine_swap : public std::conditional&>(swap1), - static_cast&>(swap2)); - } + friend void swap(Chain_vine_swap& swap1, Chain_vine_swap& swap2) noexcept + { std::swap(swap1.birthComp_, swap2.birthComp_); std::swap(swap1.deathComp_, swap2.deathComp_); } - protected: - using CP = typename std::conditional, - Dummy_chain_vine_pairing - >::type; - private: using Master_chain_matrix = typename Master_matrix::Master_chain_matrix; - std::function birthComp_; /**< for F x F & H x H. */ - std::function deathComp_; /**< for G x G. */ + std::function birthComp_; /**< for F x F & H x H. */ + std::function deathComp_; /**< for G x G. */ bool _is_negative_in_pair(Index columnIndex); - Index _positive_vine_swap(Index columnIndex1, Index columnIndex2); Index _positive_negative_vine_swap(Index columnIndex1, Index columnIndex2); Index _negative_positive_vine_swap(Index columnIndex1, Index columnIndex2); Index _negative_vine_swap(Index columnIndex1, Index columnIndex2); + void _swap_positions(ID_index pivot1, ID_index pivot2); constexpr Master_chain_matrix* _matrix() { return static_cast(this); } + constexpr const Master_chain_matrix* _matrix() const { return static_cast(this); } }; template -inline Chain_vine_swap::Chain_vine_swap() : CP(), birthComp_(), deathComp_() +inline Chain_vine_swap::Chain_vine_swap() : birthComp_(), deathComp_() { static_assert(Master_matrix::Option_list::has_column_pairings, "If barcode is not stored, at least a birth comparator has to be specified."); } template -inline Chain_vine_swap::Chain_vine_swap(std::function birthComparator, - std::function deathComparator) - : CP(), birthComp_(std::move(birthComparator)), deathComp_(std::move(deathComparator)) -{} - -template -inline Chain_vine_swap::Chain_vine_swap(const Chain_vine_swap& matrixToCopy) - : CP(static_cast(matrixToCopy)), - birthComp_(matrixToCopy.birthComp_), - deathComp_(matrixToCopy.deathComp_) -{} - -template -inline Chain_vine_swap::Chain_vine_swap(Chain_vine_swap&& other) noexcept - : CP(std::move(static_cast(other))), - birthComp_(std::move(other.birthComp_)), - deathComp_(std::move(other.deathComp_)) +inline Chain_vine_swap::Chain_vine_swap(std::function birthComparator, + std::function deathComparator) + : birthComp_(std::move(birthComparator)), deathComp_(std::move(deathComparator)) {} template inline typename Chain_vine_swap::Index Chain_vine_swap::vine_swap_with_z_eq_1_case( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { const bool col1IsNeg = _is_negative_in_pair(columnIndex1); const bool col2IsNeg = _is_negative_in_pair(columnIndex2); @@ -389,10 +347,10 @@ inline typename Chain_vine_swap::Index Chain_vine_swap inline typename Chain_vine_swap::Index Chain_vine_swap::vine_swap(Index columnIndex1, - Index columnIndex2) + Index columnIndex2) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - GUDHI_CHECK(CP::are_adjacent(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)), + GUDHI_CHECK(_matrix()->_are_adjacent(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)), std::invalid_argument( "Chain_vine_swap::vine_swap - Columns to be swapped need to be adjacent in the 'real' matrix.")); } @@ -402,13 +360,12 @@ inline typename Chain_vine_swap::Index Chain_vine_swapis_zero_entry(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + ID_index pivot1 = _matrix()->get_pivot(columnIndex1); + ID_index pivot2 = _matrix()->get_pivot(columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - ID_index pivot1 = _matrix()->get_pivot(columnIndex1); - ID_index pivot2 = _matrix()->get_pivot(columnIndex2); - - CP::negative_transpose(pivot1, pivot2); - CP::swap_positions(pivot1, pivot2); + _matrix()->_negative_transpose_barcode(pivot1, pivot2); } + _swap_positions(pivot1, pivot2); return columnIndex1; } return _negative_vine_swap(columnIndex1, columnIndex2); @@ -416,13 +373,12 @@ inline typename Chain_vine_swap::Index Chain_vine_swapis_zero_entry(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + ID_index pivot1 = _matrix()->get_pivot(columnIndex1); + ID_index pivot2 = _matrix()->get_pivot(columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - ID_index pivot1 = _matrix()->get_pivot(columnIndex1); - ID_index pivot2 = _matrix()->get_pivot(columnIndex2); - - CP::negative_positive_transpose(pivot1, pivot2); - CP::swap_positions(pivot1, pivot2); + _matrix()->_negative_positive_transpose_barcode(pivot1, pivot2); } + _swap_positions(pivot1, pivot2); return columnIndex1; } return _negative_positive_vine_swap(columnIndex1, columnIndex2); @@ -430,45 +386,34 @@ inline typename Chain_vine_swap::Index Chain_vine_swapis_zero_entry(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + ID_index pivot1 = _matrix()->get_pivot(columnIndex1); + ID_index pivot2 = _matrix()->get_pivot(columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - ID_index pivot1 = _matrix()->get_pivot(columnIndex1); - ID_index pivot2 = _matrix()->get_pivot(columnIndex2); - - CP::positive_negative_transpose(pivot1, pivot2); - CP::swap_positions(pivot1, pivot2); + _matrix()->_positive_negative_transpose_barcode(pivot1, pivot2); } + _swap_positions(pivot1, pivot2); return columnIndex1; } return _positive_negative_vine_swap(columnIndex1, columnIndex2); } if (_matrix()->is_zero_entry(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + ID_index pivot1 = _matrix()->get_pivot(columnIndex1); + ID_index pivot2 = _matrix()->get_pivot(columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - ID_index pivot1 = _matrix()->get_pivot(columnIndex1); - ID_index pivot2 = _matrix()->get_pivot(columnIndex2); - - CP::positive_transpose(pivot1, pivot2); - CP::swap_positions(pivot1, pivot2); + _matrix()->_positive_transpose_barcode(pivot1, pivot2); } + _swap_positions(pivot1, pivot2); return columnIndex1; } return _positive_vine_swap(columnIndex1, columnIndex2); } template -inline Chain_vine_swap& Chain_vine_swap::operator=(Chain_vine_swap other) -{ - CP::operator=(other); - std::swap(birthComp_, other.birthComp_); - std::swap(deathComp_, other.deathComp_); - return *this; -} - -template -inline bool Chain_vine_swap::_is_negative_in_pair(Index columnIndex) +inline bool Chain_vine_swap::_is_negative_in_pair(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - return CP::is_negative_in_pair(_matrix()->get_pivot(columnIndex)); + return _matrix()->_is_negative_in_bar(_matrix()->get_pivot(columnIndex)); } else { auto& col = _matrix()->get_column(columnIndex); if (!col.is_paired()) return false; @@ -478,20 +423,20 @@ inline bool Chain_vine_swap::_is_negative_in_pair(Index columnInd template inline typename Chain_vine_swap::Index Chain_vine_swap::_positive_vine_swap( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { auto& col1 = _matrix()->get_column(columnIndex1); auto& col2 = _matrix()->get_column(columnIndex2); - if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::swap_positions(col1.get_pivot(), col2.get_pivot()); - } - // TODO: factorize the cases. But for debug it is much more easier to understand what is happening splitted like this + _swap_positions(col1.get_pivot(), col2.get_pivot()); + // TODO: factorize the cases + // But for debug it is much more easier to understand what is happening when split like this if (!col1.is_paired()) { // F x * bool hasSmallerBirth; if constexpr (Master_matrix::Option_list::has_column_pairings) { - // this order because position were swapped with CP::swap_positions - hasSmallerBirth = (CP::birth(col2.get_pivot()) < CP::birth(col1.get_pivot())); + // this order because position were swapped with swap_positions + hasSmallerBirth = (_matrix()->_birth_val(col2.get_pivot()) < _matrix()->_birth_val(col1.get_pivot())); } else { hasSmallerBirth = birthComp_(columnIndex1, columnIndex2); } @@ -499,7 +444,7 @@ inline typename Chain_vine_swap::Index Chain_vine_swapadd_to(columnIndex1, columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + _matrix()->_positive_transpose_barcode(col1.get_pivot(), col2.get_pivot()); } return columnIndex1; } @@ -511,26 +456,25 @@ inline typename Chain_vine_swap::Index Chain_vine_swap(this)->add_to(columnIndex1, columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + _matrix()->_positive_transpose_barcode(col1.get_pivot(), col2.get_pivot()); } return columnIndex1; } bool hasSmallerDeath; if constexpr (Master_matrix::Option_list::has_column_pairings) { - // this order because position were swapped with CP::swap_positions - hasSmallerDeath = (CP::death(col2.get_pivot()) < CP::death(col1.get_pivot())); + // this order because position were swapped with swap_positions + hasSmallerDeath = (_matrix()->_death_val(col2.get_pivot()) < _matrix()->_death_val(col1.get_pivot())); } else { hasSmallerDeath = deathComp_(columnIndex1, columnIndex2); } // G x G - if (hasSmallerDeath) - { + if (hasSmallerDeath) { _matrix()->add_to(col1.get_paired_chain_index(), col2.get_paired_chain_index()); _matrix()->add_to(columnIndex1, columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + _matrix()->_positive_transpose_barcode(col1.get_pivot(), col2.get_pivot()); } return columnIndex1; } @@ -543,37 +487,37 @@ inline typename Chain_vine_swap::Index Chain_vine_swap inline typename Chain_vine_swap::Index Chain_vine_swap::_positive_negative_vine_swap( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { _matrix()->add_to(columnIndex1, columnIndex2); + ID_index pivot1 = _matrix()->get_pivot(columnIndex1); + ID_index pivot2 = _matrix()->get_pivot(columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - ID_index pivot1 = _matrix()->get_pivot(columnIndex1); - ID_index pivot2 = _matrix()->get_pivot(columnIndex2); - - CP::positive_negative_transpose(pivot1, pivot2); - CP::swap_positions(pivot1, pivot2); + _matrix()->_positive_negative_transpose_barcode(pivot1, pivot2); } + _swap_positions(pivot1, pivot2); return columnIndex1; } template inline typename Chain_vine_swap::Index Chain_vine_swap::_negative_positive_vine_swap( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { _matrix()->add_to(columnIndex2, columnIndex1); - if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::swap_positions(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)); - } + _swap_positions(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)); return columnIndex2; } template inline typename Chain_vine_swap::Index Chain_vine_swap::_negative_vine_swap( - Index columnIndex1, Index columnIndex2) + Index columnIndex1, + Index columnIndex2) { auto& col1 = _matrix()->get_column(columnIndex1); auto& col2 = _matrix()->get_column(columnIndex2); @@ -583,22 +527,19 @@ inline typename Chain_vine_swap::Index Chain_vine_swap_birth_val(col1.get_pivot()) < _matrix()->_birth_val(col2.get_pivot())); } else { hasSmallerBirth = birthComp_(pairedIndex1, pairedIndex2); } - if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::swap_positions(col1.get_pivot(), col2.get_pivot()); - } + _swap_positions(col1.get_pivot(), col2.get_pivot()); - if (hasSmallerBirth) - { + if (hasSmallerBirth) { _matrix()->add_to(pairedIndex1, pairedIndex2); _matrix()->add_to(columnIndex1, columnIndex2); if constexpr (Master_matrix::Option_list::has_column_pairings) { - CP::negative_transpose(col1.get_pivot(), col2.get_pivot()); + _matrix()->_negative_transpose_barcode(col1.get_pivot(), col2.get_pivot()); } return columnIndex1; @@ -610,6 +551,20 @@ inline typename Chain_vine_swap::Index Chain_vine_swap +inline void Chain_vine_swap::_swap_positions(ID_index pivot1, ID_index pivot2) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings || + Master_matrix::Option_list::can_retrieve_representative_cycles) { + auto& map = _matrix()->map_; + if constexpr (Master_matrix::Option_list::has_map_column_container) { + std::swap(map.at(pivot1), map.at(pivot2)); + } else { + std::swap(map[pivot1], map[pivot2]); + } + } +} + } // namespace persistence_matrix } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h index 5fb6d6ac..cffb79de 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h @@ -30,11 +30,12 @@ namespace persistence_matrix { * Inherited instead of @ref Chain_column_extra_properties, when the columns are not meant for * @ref chainmatrix "chain matrices". */ -struct Dummy_chain_properties -{ +struct Dummy_chain_properties { Dummy_chain_properties([[maybe_unused]] int pivot = 0, [[maybe_unused]] int pair = 0) {} - friend void swap([[maybe_unused]] Dummy_chain_properties& col1, [[maybe_unused]] Dummy_chain_properties& col2) {} + friend void swap([[maybe_unused]] Dummy_chain_properties& col1, + [[maybe_unused]] Dummy_chain_properties& col2) noexcept + {} }; /** @@ -46,102 +47,131 @@ struct Dummy_chain_properties * * The columns of a @ref chainmatrix "chain matrix" are partitioned in three sets: \f$ F \f$, \f$ G \f$ and \f$ H \f$ * with a bijection between \f$ G \f$ and \f$ H \f$. If a column is in \f$ F \f$, the value of - * `Chain_column_extra_properties::pairedColumn_` is `-1`, while the value corresponds to the @ref MatIdx index of - * the image of the bijection if the column is in either \f$ G \f$ or \f$ H \f$. See @cite zigzag for - * more details. - * + * `Chain_column_extra_properties::pairedColumn_` is @ref Matrix::get_null_value "null index", while the value + * corresponds to the @ref MatIdx index of the image of the bijection if the column is in either \f$ G \f$ or \f$ H \f$. + * See @cite zigzag for more details. + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Chain_column_extra_properties +class Chain_column_extra_properties { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ /** - * @brief Default constructor. Sets the pivot and pair to -1, which means "not existing". + * @brief Default constructor. Sets the pivot and pair to @ref Matrix::get_null_value "null index", which means + * "not existing". */ - Chain_column_extra_properties() : pivot_(-1), pairedColumn_(-1) {} + Chain_column_extra_properties() + : pivot_(Master_matrix::template get_null_value()), + pairedColumn_(Master_matrix::template get_null_value()) + {} + /** - * @brief Constructor setting the pivot at the given value and the pair to -1 (i.e. not paired). - * + * @brief Constructor setting the pivot at the given value and the pair to @ref Matrix::get_null_value "null index" + * (i.e. not paired). + * * @param pivot @ref rowindex "Row index" of the pivot. Corresponds to the @ref IDIdx index of the cell represented * by the column. */ - Chain_column_extra_properties(ID_index pivot) : pivot_(pivot), pairedColumn_(-1) {} + Chain_column_extra_properties(ID_index pivot) + : pivot_(pivot), pairedColumn_(Master_matrix::template get_null_value()) + {} + /** * @brief Constructor setting the pivot and the pair at the given values. - * + * * @param pivot @ref rowindex "Row index" of the pivot. Corresponds to the @ref IDIdx index of the cell represented * by the column. * @param pair @ref MatIdx index of the pair of the column. */ Chain_column_extra_properties(ID_index pivot, Index pair) : pivot_(pivot), pairedColumn_(pair) {} + /** * @brief Copy constructor. - * + * * @param col Column to copy. */ - Chain_column_extra_properties(const Chain_column_extra_properties& col) - : pivot_(col.pivot_), pairedColumn_(col.pairedColumn_) {} + Chain_column_extra_properties(const Chain_column_extra_properties& col) = default; + /** * @brief Move constructor. - * + * * @param col Column to move. */ - Chain_column_extra_properties(Chain_column_extra_properties&& col) - : pivot_(std::exchange(col.pivot_, -1)), pairedColumn_(std::exchange(col.pairedColumn_, -1)) {} + Chain_column_extra_properties(Chain_column_extra_properties&& col) noexcept + : pivot_(std::exchange(col.pivot_, Master_matrix::template get_null_value())), + pairedColumn_(std::exchange(col.pairedColumn_, Master_matrix::template get_null_value())) + {} + + ~Chain_column_extra_properties() = default; /** - * @brief Returns -1 if the column is not paired, the @ref MatIdx of the pair otherwise. - * - * @return -1 if the column is not paired, the @ref MatIdx of the pair otherwise. + * @brief Returns @ref Matrix::get_null_value "null index" if the column is not paired, the @ref MatIdx of the pair + * otherwise. + * + * @return @ref Matrix::get_null_value "null index" if the column is not paired, the @ref MatIdx of the pair + * otherwise. */ Index get_paired_chain_index() const { return pairedColumn_; } + /** * @brief Indicates if the column is paired or not. - * + * * @return true If the column is paired. * @return false Otherwise. */ - bool is_paired() const { return pairedColumn_ != static_cast(-1); } + bool is_paired() const { return pairedColumn_ != Master_matrix::template get_null_value(); } + /** * @brief Sets the value of the pair. - * + * * @param other_col @ref MatIdx of the pair column. */ void assign_paired_chain(Index other_col) { pairedColumn_ = other_col; } + /** * @brief Un-pairs a column. */ - void unassign_paired_chain() { pairedColumn_ = -1; }; + void unassign_paired_chain() { pairedColumn_ = Master_matrix::template get_null_value(); }; /** * @brief Assign operator. */ - Chain_column_extra_properties& operator=(const Chain_column_extra_properties& other) { - pivot_ = other.pivot_; - pairedColumn_ = other.pairedColumn_; + Chain_column_extra_properties& operator=(const Chain_column_extra_properties& other) = default; + + /** + * @brief Move assign operator. + */ + Chain_column_extra_properties& operator=(Chain_column_extra_properties&& other) noexcept + { + pivot_ = std::exchange(other.pivot_, Master_matrix::template get_null_value()); + pairedColumn_ = std::exchange(other.pairedColumn_, Master_matrix::template get_null_value()); return *this; } + /** * @brief Swap operator. */ - friend void swap(Chain_column_extra_properties& col1, Chain_column_extra_properties& col2) { + friend void swap(Chain_column_extra_properties& col1, Chain_column_extra_properties& col2) noexcept + { std::swap(col1.pivot_, col2.pivot_); std::swap(col1.pairedColumn_, col2.pairedColumn_); } protected: - ID_index get_pivot() const { return pivot_; } - void swap_pivots(Chain_column_extra_properties& other) { std::swap(pivot_, other.pivot_); } + ID_index _get_pivot() const { return pivot_; } + + void _swap_pivots(Chain_column_extra_properties& other) { std::swap(pivot_, other.pivot_); } private: - ID_index pivot_; /**< @ref IDIdx index associated to the chain */ - Index pairedColumn_; /**< Represents the (F, G x H) partition of the columns. - -1 if in F, @ref MatIdx of image of bijection otherwise. - The pivot of a column in G will always be smaller than the pivot of its image in H. */ + ID_index pivot_; /**< @ref IDIdx index associated to the chain */ + Index pairedColumn_; /**< Represents the (F, G x H) partition of the columns. + @ref Matrix::get_null_value "null index" if in F, @ref MatIdx of image of bijection + otherwise. The pivot of a column in G will always be smaller than the pivot of its + image in H. */ }; } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h index 7dca7104..8fb64b54 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/column_dimension_holder.h @@ -29,13 +29,16 @@ namespace persistence_matrix { * @brief Empty structure. * Inherited instead of @ref Column_dimension_holder, when the columns are not storing a dimension. */ -struct Dummy_dimension_holder -{ - Dummy_dimension_holder() {} +struct Dummy_dimension_holder { + Dummy_dimension_holder() = default; + template - Dummy_dimension_holder([[maybe_unused]] Dimension dim) {} + Dummy_dimension_holder([[maybe_unused]] Dimension dim) + {} - friend void swap([[maybe_unused]] Dummy_dimension_holder& col1, [[maybe_unused]] Dummy_dimension_holder& col2) {} + friend void swap([[maybe_unused]] Dummy_dimension_holder& col1, + [[maybe_unused]] Dummy_dimension_holder& col2) noexcept + {} }; /** @@ -43,40 +46,49 @@ struct Dummy_dimension_holder * @ingroup persistence_matrix * * @brief Class managing the dimension access of a column. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -struct Column_dimension_holder -{ - using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ +struct Column_dimension_holder { + using Dimension = typename Master_matrix::Dimension; /**< Dimension value type. */ /** - * @brief Default constructor. Sets the dimension to 0 for @ref boundarymatrix "boundary matrices" and to -1 for @ref chainmatrix "chain matrices". + * @brief Default constructor. Sets the dimension to 0 for @ref boundarymatrix "boundary matrices" and to + * @ref Matrix::get_null_value "null index" for @ref chainmatrix "chain matrices". */ - Column_dimension_holder() : dim_(Master_matrix::Option_list::is_of_boundary_type ? 0 : -1) {} + Column_dimension_holder() + : dim_(Master_matrix::Option_list::is_of_boundary_type ? 0 : Master_matrix::template get_null_value()) + {} + /** * @brief Constructor setting the dimension to the given value. - * + * * @param dim Dimension of the column. */ Column_dimension_holder(Dimension dim) : dim_(dim) {} + /** * @brief Copy constructor. - * + * * @param col Column to copy. */ - Column_dimension_holder(const Column_dimension_holder& col) : dim_(col.dim_) {} + Column_dimension_holder(const Column_dimension_holder& col) = default; + /** * @brief Move constructor. - * + * * @param col Column to move. */ - Column_dimension_holder(Column_dimension_holder&& col) : dim_(std::exchange(col.dim_, -1)) {} + Column_dimension_holder(Column_dimension_holder&& col) noexcept + : dim_(std::exchange(col.dim_, Master_matrix::template get_null_value())) + {} + + ~Column_dimension_holder() = default; /** * @brief Returns the dimension of the column. - * + * * @return The dimension of the column. */ Dimension get_dimension() const { return dim_; } @@ -84,20 +96,30 @@ struct Column_dimension_holder /** * @brief Assign operator. */ - Column_dimension_holder& operator=(const Column_dimension_holder& other) { - dim_ = other.dim_; + Column_dimension_holder& operator=(const Column_dimension_holder& other) = default; + + /** + * @brief Move assign operator. + */ + Column_dimension_holder& operator=(Column_dimension_holder&& other) noexcept + { + dim_ = std::exchange(other.dim_, Master_matrix::template get_null_value()); return *this; } + /** * @brief Swap operator. */ - friend void swap(Column_dimension_holder& col1, Column_dimension_holder& col2) { std::swap(col1.dim_, col2.dim_); } + friend void swap(Column_dimension_holder& col1, Column_dimension_holder& col2) noexcept + { + std::swap(col1.dim_, col2.dim_); + } protected: - void swap_dimension(Column_dimension_holder& other) { std::swap(dim_, other.dim_); } + void _swap_dimension(Column_dimension_holder& other) { std::swap(dim_, other.dim_); } private: - Dimension dim_; /**< Dimension of the column. */ + Dimension dim_; /**< Dimension of the column. */ }; } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h index bb7670cf..cad34ddf 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/column_utilities.h @@ -50,10 +50,10 @@ void _generic_merge_entry_to_column(Column& targetColumn, typename Column::Entry* targetEntry = _get_entry(itTarget); if (targetEntry->get_row_index() < itSource->get_row_index()) { - process_target(targetEntry); + std::forward(process_target)(targetEntry); ++itTarget; } else if (targetEntry->get_row_index() > itSource->get_row_index()) { - process_source(itSource, itTarget); + std::forward(process_source)(itSource, itTarget); ++itSource; } else { if constexpr (Column::Master::Option_list::is_z2) { @@ -63,14 +63,14 @@ void _generic_merge_entry_to_column(Column& targetColumn, } targetColumn._delete_entry(itTarget); } else { - update_target1(targetEntry->get_element(), itSource); + std::forward(update_target1)(targetEntry->get_element(), itSource); if (targetEntry->get_element() == Column::Field_operators::get_additive_identity()) { if constexpr (Column::Master::isNonBasic && !Column::Master::Option_list::is_of_boundary_type) { if (targetEntry->get_row_index() == targetColumn.get_pivot()) pivotIsZeroed = true; } targetColumn._delete_entry(itTarget); } else { - update_target2(targetEntry); + std::forward(update_target2)(targetEntry); if constexpr (Column::Master::Option_list::has_row_access) targetColumn.update_entry(*targetEntry); ++itTarget; } @@ -95,15 +95,20 @@ bool _generic_add_to_column(const Entry_range& source, auto itTarget = target.begin(); auto itSource = source.begin(); while (itTarget != target.end() && itSource != source.end()) { - _generic_merge_entry_to_column(targetColumn, itSource, itTarget, - process_target, process_source, update_target1, update_target2, + _generic_merge_entry_to_column(targetColumn, + itSource, + itTarget, + std::forward(process_target), + std::forward(process_source), + std::forward(update_target1), + std::forward(update_target2), pivotIsZeroed); } - finish_target(itTarget); + std::forward(finish_target)(itTarget); while (itSource != source.end()) { - process_source(itSource, target.end()); + std::forward(process_source)(itSource, target.end()); ++itSource; } @@ -129,8 +134,7 @@ bool _add_to_column(const Entry_range& source, Column& targetColumn) targetColumn.operators_->add_inplace(targetElement, itSource->get_element()); }, [&]([[maybe_unused]] typename Column::Entry* entryTarget) {}, - [&]([[maybe_unused]] typename Column::Column_support::iterator& itTarget) {} - ); + [&]([[maybe_unused]] typename Column::Column_support::iterator& itTarget) {}); } template @@ -138,7 +142,7 @@ bool _multiply_target_and_add_to_column(const typename Column::Field_element& va const Entry_range& source, Column& targetColumn) { - if (val == 0u) { + if (val == 0U) { if constexpr (Column::Master::isNonBasic && !Column::Master::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); // this would not only mess up the base, but also the pivots stored. @@ -170,8 +174,7 @@ bool _multiply_target_and_add_to_column(const typename Column::Field_element& va if constexpr (Column::Master::Option_list::has_row_access) targetColumn.update_entry(*targetEntry); itTarget++; } - } - ); + }); } template @@ -179,7 +182,7 @@ bool _multiply_source_and_add_to_column(const typename Column::Field_element& va const Entry_range& source, Column& targetColumn) { - if (val == 0u) { + if (val == 0U) { return false; } diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h index 81083d3d..4e9cce35 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/entry_types.h @@ -33,11 +33,12 @@ namespace persistence_matrix { * @brief Empty structure. * Inherited instead of @ref Entry_column_index, when the row access is disabled. */ -struct Dummy_entry_column_index_mixin -{ - Dummy_entry_column_index_mixin() {} +struct Dummy_entry_column_index_mixin { + Dummy_entry_column_index_mixin() = default; + template - Dummy_entry_column_index_mixin([[maybe_unused]] Index columnIndex) {} + Dummy_entry_column_index_mixin([[maybe_unused]] Index columnIndex) + {} }; /** @@ -46,135 +47,99 @@ struct Dummy_entry_column_index_mixin * @brief Empty structure. * Inherited instead of @ref Entry_field_element, when @ref PersistenceMatrixOptions::is_z2 is true. */ -struct Dummy_entry_field_element_mixin -{ - Dummy_entry_field_element_mixin() {} +struct Dummy_entry_field_element_mixin { + Dummy_entry_field_element_mixin() = default; + template - Dummy_entry_field_element_mixin([[maybe_unused]] Field_element t) {} + Dummy_entry_field_element_mixin([[maybe_unused]] Field_element t) + {} }; /** * @ingroup persistence_matrix * * @brief Class managing the column index access of an entry. - * + * * @tparam Index @ref MatIdx index type. */ template -class Entry_column_index +class Entry_column_index { public: /** * @brief Default constructor. Sets to the column index to -1. */ - Entry_column_index() : columnIndex_(-1){}; + Entry_column_index() : columnIndex_(-1) {}; /** * @brief Stores the given column index. - * + * * @param columnIndex Column index of the entry. */ - Entry_column_index(Index columnIndex) : columnIndex_(columnIndex){}; - /** - * @brief Copy constructor. - * - * @param entry Entry to copy. - */ - Entry_column_index(const Entry_column_index& entry) : columnIndex_(entry.columnIndex_){}; - /** - * @brief Move constructor. - * - * @param entry Entry to move. - */ - Entry_column_index(Entry_column_index&& entry) noexcept : columnIndex_(std::exchange(entry.columnIndex_, 0)){}; + Entry_column_index(Index columnIndex) : columnIndex_(columnIndex) {}; /** * @brief Returns the @ref MatIdx column index stored in the entry. - * + * * @return Column index of the entry. */ Index get_column_index() const { return columnIndex_; }; + /** * @brief Sets the column index to the given value. - * + * * @param columnIndex Column index of the entry. */ void set_column_index(Index columnIndex) { columnIndex_ = columnIndex; } - /** - * @brief Assign operator. - */ - Entry_column_index& operator=(Entry_column_index other) { - std::swap(columnIndex_, other.columnIndex_); - return *this; - }; - private: - Index columnIndex_; /**< Column index. */ + Index columnIndex_; /**< Column index. */ }; /** * @ingroup persistence_matrix * * @brief Class managing the value access of an entry. - * + * * @tparam Field_element Type of an entry value. */ template -class Entry_field_element +class Entry_field_element { public: /** * @brief Default constructor. Sets to the element to 0. */ - Entry_field_element() : element_(0){}; + Entry_field_element() : element_(0) {}; /** * @brief Stores the given element. - * + * * @param element Value to store. */ - Entry_field_element(Field_element element) : element_(element){}; - /** - * @brief Copy constructor. - * - * @param entry Entry to copy. - */ - Entry_field_element(const Entry_field_element& entry) : element_(entry.element_){}; - /** - * @brief Move constructor. - * - * @param entry Entry to move. - */ - Entry_field_element(Entry_field_element&& entry) noexcept : element_(std::move(entry.element_)){}; + Entry_field_element(Field_element element) : element_(std::move(element)) {}; /** * @brief Returns the value stored in the entry. - * + * * @return Reference to the value of the entry. */ Field_element& get_element() { return element_; }; + /** * @brief Returns the value stored in the entry. - * + * * @return Const reference to the value of the entry. */ const Field_element& get_element() const { return element_; }; + /** * @brief Sets the value. - * + * * @param element Value to store. */ void set_element(const Field_element& element) { element_ = element; } - /** - * @brief Assign operator. - */ - Entry_field_element& operator=(Entry_field_element other) { - std::swap(element_, other.element_); - return *this; - }; - private: - Field_element element_; /**< Value of the entry. */ + Field_element element_; /**< Value of the entry. */ }; /** @@ -184,70 +149,73 @@ class Entry_field_element * @brief %Matrix entry class. Stores by default only the row index it belongs to, but can also store its * column index when the row access is enabled, as well as its value when they are different from only 0 and 1. * Zero-valued entries are never made explicit in the matrix. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template class Entry : public Master_matrix::Entry_column_index_option, - public Master_matrix::Entry_field_element_option, - public Master_matrix::Row_hook, - public Master_matrix::Column_hook + public Master_matrix::Entry_field_element_option, + public Master_matrix::Row_hook, + public Master_matrix::Column_hook { private: using col_opt = typename Master_matrix::Entry_column_index_option; using field_opt = typename Master_matrix::Entry_field_element_option; public: - using Master = Master_matrix; /**< Access to options from outside. */ - using Index = typename Master_matrix::Index; /**< Column index type. */ - using ID_index = typename Master_matrix::ID_index; /**< Row index type. */ - using Field_element = typename Master_matrix::Element; /**< Value type. */ + using Master = Master_matrix; /**< Access to options from outside. */ + using Index = typename Master_matrix::Index; /**< Column index type. */ + using ID_index = typename Master_matrix::ID_index; /**< Row index type. */ + using Field_element = typename Master_matrix::Element; /**< Value type. */ /** * @brief Constructs an entry with all attributes at default values. */ - Entry(){}; + Entry() = default; /** * @brief Constructs an entry with given row index. Other possible attributes are set at default values. - * + * * @param rowIndex @ref rowindex "Row index" of the entry. */ - Entry(ID_index rowIndex) : col_opt(), field_opt(), rowIndex_(rowIndex){}; + Entry(ID_index rowIndex) : col_opt(), field_opt(), rowIndex_(rowIndex) {}; /** * @brief Constructs an entry with given row and column index. Other possible attributes are set at default values. - * + * * @param columnIndex Column index of the entry. * @param rowIndex @ref rowindex "Row index" of the entry. */ - Entry(Index columnIndex, ID_index rowIndex) : col_opt(columnIndex), field_opt(), rowIndex_(rowIndex){}; + Entry(Index columnIndex, ID_index rowIndex) : col_opt(columnIndex), field_opt(), rowIndex_(rowIndex) {}; /** * @brief Copy constructor. - * + * * @param entry Entry to copy. */ Entry(const Entry& entry) : col_opt(static_cast(entry)), field_opt(static_cast(entry)), - rowIndex_(entry.rowIndex_){}; + rowIndex_(entry.rowIndex_) {}; /** * @brief Move constructor. - * + * * @param entry Entry to move. */ Entry(Entry&& entry) noexcept : col_opt(std::move(static_cast(entry))), field_opt(std::move(static_cast(entry))), - rowIndex_(std::exchange(entry.rowIndex_, 0)){}; + rowIndex_(std::exchange(entry.rowIndex_, 0)) {}; + + ~Entry() = default; /** * @brief Returns the row index stored in the entry. - * + * * @return @ref rowindex "Row index" of the entry. */ ID_index get_row_index() const { return rowIndex_; }; + /** * @brief Sets the row index stored in the entry. - * + * * @param rowIndex @ref rowindex "Row index" of the entry. */ void set_row_index(ID_index rowIndex) { rowIndex_ = rowIndex; }; @@ -255,25 +223,38 @@ class Entry : public Master_matrix::Entry_column_index_option, /** * @brief Assign operator. */ - Entry& operator=(Entry other) { - col_opt::operator=(other); - field_opt::operator=(other); + Entry& operator=(Entry other) & + { + col_opt::operator=(std::move(other)); + field_opt::operator=(std::move(other)); std::swap(rowIndex_, other.rowIndex_); return *this; }; + /** + * @brief Move assign operator. + */ + Entry& operator=(Entry&& other) && noexcept + { + col_opt::operator=(std::move(other)); + field_opt::operator=(std::move(other)); + rowIndex_ = std::exchange(other.rowIndex_, 0); + return *this; + }; + /** * @brief Strictly smaller than comparator. - * + * * @param c1 First entry to compare. * @param c2 Second entry to compare. * @return true If the row index of the first entry is strictly smaller than the row index of the second entry. * @return false Otherwise. */ friend bool operator<(const Entry& c1, const Entry& c2) { return c1.get_row_index() < c2.get_row_index(); } + /** * @brief Equality comparator. - * + * * @param c1 First entry to compare. * @param c2 Second entry to compare. * @return true If the row index of the first entry is equal to the row index of the second entry. @@ -283,16 +264,18 @@ class Entry : public Master_matrix::Entry_column_index_option, /** * @brief Converts the entry into a row index. - * + * * @return The row index of the entry. */ operator ID_index() const { return rowIndex_; } + /** * @brief Converts the entry into a pair of row index and entry value. - * + * * @return A std::pair with first element the row index and second element the value. */ - operator std::pair() const { + operator std::pair() const + { if constexpr (Master_matrix::Option_list::is_z2) { return {rowIndex_, 1}; } else { @@ -301,7 +284,7 @@ class Entry : public Master_matrix::Entry_column_index_option, } private: - ID_index rowIndex_; /**< Row index of the entry. */ + ID_index rowIndex_; /**< Row index of the entry. */ }; } // namespace persistence_matrix @@ -314,12 +297,13 @@ class Entry : public Master_matrix::Entry_column_index_option, * * The entries are differentiated by their row indices only. For example, two entries with the same row index * but different column indices have the same hash value. - * + * * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Entry. */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Entry& entry) const { + std::size_t operator()(const Gudhi::persistence_matrix::Entry& entry) const + { return std::hash()(entry.get_row_index()); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h index c7b0fa8e..8a2325f2 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/heap_column.h @@ -26,6 +26,7 @@ #include +#include #include namespace Gudhi { @@ -44,7 +45,6 @@ namespace persistence_matrix { * row index. Additionally, the given entry range added into the heap does not need to be somehow ordered. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Heap_column : public Master_matrix::Column_dimension_option, public Master_matrix::Chain_column_option @@ -73,7 +73,7 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master template Heap_column(const Container& nonZeroRowIndices, Column_settings* colSettings); template - Heap_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Heap_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); Heap_column(const Heap_column& column, Column_settings* colSettings = nullptr); Heap_column(Heap_column&& column) noexcept; ~Heap_column(); @@ -87,7 +87,7 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master Column_settings* colSettings); template Heap_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -100,10 +100,11 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; bool is_empty(); - std::size_t size() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -138,7 +139,8 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master std::size_t compute_hash_value(); - friend bool operator==(const Heap_column& c1, const Heap_column& c2) { + friend bool operator==(const Heap_column& c1, const Heap_column& c2) + { if (&c1 == &c2) return true; Heap_column cc1(c1), cc2(c2); @@ -171,7 +173,9 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master c2.entryPool_->destroy(p2); return false; } - friend bool operator<(const Heap_column& c1, const Heap_column& c2) { + + friend bool operator<(const Heap_column& c1, const Heap_column& c2) + { if (&c1 == &c2) return false; // lexicographical order but starting from last value and not first @@ -217,8 +221,10 @@ class Heap_column : public Master_matrix::Column_dimension_option, public Master // Disabled with row access. Heap_column& operator=(const Heap_column& other); + Heap_column& operator=(Heap_column&& other) noexcept; - friend void swap(Heap_column& col1, Heap_column& col2) { + friend void swap(Heap_column& col1, Heap_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -306,9 +312,13 @@ inline Heap_column::Heap_column(const Container& nonZeroRowIndice : Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -368,7 +378,8 @@ inline Heap_column::Heap_column(Heap_column&& column) noexcept insertsSinceLastPrune_(std::exchange(column.insertsSinceLastPrune_, 0)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template template @@ -379,9 +390,13 @@ inline Heap_column::Heap_column(Index columnIndex, : Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -417,9 +432,13 @@ inline Heap_column::Heap_column(Index columnIndex, : Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -501,7 +520,7 @@ inline std::vector::Field_element> Heap_colu } if (pivotLength) { - while (!container.empty() && container.back() == 0u) container.pop_back(); + while (!container.empty() && container.back() == 0U) container.pop_back(); } return container; @@ -608,9 +627,9 @@ inline typename Heap_column::ID_index Heap_column: std::push_heap(column_.begin(), column_.end(), entryPointerComp_); return pivot->get_row_index(); } - return -1; + return Master_matrix::template get_null_value(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -633,9 +652,9 @@ inline typename Heap_column::Field_element Heap_column(-1)) return sum; + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return sum; for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) operators_->add_inplace(sum, entry->get_element()); + if (entry->get_row_index() == Chain_opt::_get_pivot()) operators_->add_inplace(sum, entry->get_element()); } return sum; // should not be 0 if properly used. } @@ -711,8 +730,8 @@ inline Heap_column& Heap_column::operator+=(Heap_c if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -788,16 +807,16 @@ inline Heap_column& Heap_column::multiply_target_a if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -847,14 +866,14 @@ inline Heap_column& Heap_column::multiply_source_a if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -875,6 +894,8 @@ inline void Heap_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + Entry* newEntry = entryPool_->construct(entry.get_row_index()); if constexpr (!Master_matrix::Option_list::is_z2) { newEntry->set_element(operators_->get_value(entry.get_element())); @@ -888,6 +909,9 @@ inline Heap_column& Heap_column::operator=(const H { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -915,6 +939,29 @@ inline Heap_column& Heap_column::operator=(const H return *this; } +template +inline Heap_column& Heap_column::operator=(Heap_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) entryPool_->destroy(entry); + } + + column_ = std::move(other.column_); + insertsSinceLastPrune_ = std::exchange(other.insertsSinceLastPrune_, 0); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + template inline std::size_t Heap_column::compute_hash_value() { @@ -1007,12 +1054,12 @@ inline bool Heap_column::_add(const Entry_range& column) ++insertsSinceLastPrune_; if constexpr (Master_matrix::Option_list::is_z2) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry.get_row_index() == Chain_opt::get_pivot()) pivotVal = !pivotVal; + if (entry.get_row_index() == Chain_opt::_get_pivot()) pivotVal = !pivotVal; } column_.push_back(entryPool_->construct(entry.get_row_index())); } else { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry.get_row_index() == Chain_opt::get_pivot()) operators_->add_inplace(pivotVal, entry.get_element()); + if (entry.get_row_index() == Chain_opt::_get_pivot()) operators_->add_inplace(pivotVal, entry.get_element()); } column_.push_back(entryPool_->construct(entry.get_row_index())); column_.back()->set_element(entry.get_element()); @@ -1032,7 +1079,7 @@ template template inline bool Heap_column::_multiply_target_and_add(const Field_element& val, const Entry_range& column) { - if (val == 0u) { + if (val == 0U) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); // this would not only mess up the base, but also the pivots stored. @@ -1056,14 +1103,14 @@ inline bool Heap_column::_multiply_target_and_add(const Field_ele for (Entry* entry : column_) { operators_->multiply_inplace(entry->get_element(), val); if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry->get_row_index() == Chain_opt::get_pivot()) operators_->add_inplace(pivotVal, entry->get_element()); + if (entry->get_row_index() == Chain_opt::_get_pivot()) operators_->add_inplace(pivotVal, entry->get_element()); } } for (const Entry& entry : column) { ++insertsSinceLastPrune_; if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry.get_row_index() == Chain_opt::get_pivot()) operators_->add_inplace(pivotVal, entry.get_element()); + if (entry.get_row_index() == Chain_opt::_get_pivot()) operators_->add_inplace(pivotVal, entry.get_element()); } column_.push_back(entryPool_->construct(entry.get_row_index())); column_.back()->set_element(entry.get_element()); @@ -1082,7 +1129,7 @@ template template inline bool Heap_column::_multiply_source_and_add(const Entry_range& column, const Field_element& val) { - if (val == 0u || column.begin() == column.end()) { + if (val == 0U || column.begin() == column.end()) { return false; } if (column_.empty()) { // chain should never enter here. @@ -1107,7 +1154,7 @@ inline bool Heap_column::_multiply_source_and_add(const Entry_ran column_.back()->set_element(entry.get_element()); operators_->multiply_inplace(column_.back()->get_element(), val); if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry.get_row_index() == Chain_opt::get_pivot()) { + if (entry.get_row_index() == Chain_opt::_get_pivot()) { operators_->add_inplace(pivotVal, column_.back()->get_element()); } } @@ -1116,7 +1163,7 @@ inline bool Heap_column::_multiply_source_and_add(const Entry_ran if (2 * insertsSinceLastPrune_ > column_.size()) _prune(); - return pivotVal == 0u; + return pivotVal == 0U; } } // namespace persistence_matrix @@ -1132,7 +1179,8 @@ inline bool Heap_column::_multiply_source_and_add(const Entry_ran */ template struct std::hash > { - size_t operator()(Gudhi::persistence_matrix::Heap_column& column) const { + size_t operator()(Gudhi::persistence_matrix::Heap_column& column) const + { return column.compute_hash_value(); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h index 77305801..f7e164fd 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_list_column.h @@ -41,7 +41,6 @@ namespace persistence_matrix { * are stored uniquely in the underlying container. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Intrusive_list_column : public Master_matrix::Row_access_option, @@ -60,7 +59,8 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, private: using Field_operators = typename Master_matrix::Field_operators; using Column_support = - boost::intrusive::list, + boost::intrusive::list, boost::intrusive::base_hook >; using Entry_constructor = typename Master_matrix::Entry_constructor; @@ -79,10 +79,10 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Intrusive_list_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Intrusive_list_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Intrusive_list_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -97,11 +97,12 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -134,7 +135,8 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const Intrusive_list_column& c1, const Intrusive_list_column& c2) { + friend bool operator==(const Intrusive_list_column& c1, const Intrusive_list_column& c2) + { if (&c1 == &c2) return true; if constexpr (Master_matrix::Option_list::is_z2) { @@ -151,7 +153,9 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, return true; } } - friend bool operator<(const Intrusive_list_column& c1, const Intrusive_list_column& c2) { + + friend bool operator<(const Intrusive_list_column& c1, const Intrusive_list_column& c2) + { if (&c1 == &c2) return false; if constexpr (Master_matrix::Option_list::is_z2) { @@ -171,8 +175,10 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, // Disabled with row access. Intrusive_list_column& operator=(const Intrusive_list_column& other); + Intrusive_list_column& operator=(Intrusive_list_column&& other) noexcept; - friend void swap(Intrusive_list_column& col1, Intrusive_list_column& col2) { + friend void swap(Intrusive_list_column& col1, Intrusive_list_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -200,10 +206,11 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, // The disposer object function for boost intrusive container struct Delete_disposer { - Delete_disposer() {}; + Delete_disposer() = default; Delete_disposer(Intrusive_list_column* col) : col_(col) {}; - void operator()(Entry* delete_this) { + void operator()(Entry* delete_this) + { if constexpr (Master_matrix::Option_list::has_row_access) col_->unlink(delete_this); col_->entryPool_->destroy(delete_this); } @@ -217,13 +224,13 @@ class Intrusive_list_column : public Master_matrix::Row_access_option, template friend void _generic_merge_entry_to_column(Column& targetColumn, - Entry_iterator& itSource, - typename Column::Column_support::iterator& itTarget, - F1&& process_target, - F2&& process_source, - F3&& update_target1, - F4&& update_target2, - bool& pivotIsZeroed); + Entry_iterator& itSource, + typename Column::Column_support::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); template friend bool _generic_add_to_column(const Entry_range& source, Column& targetColumn, @@ -305,9 +312,13 @@ inline Intrusive_list_column::Intrusive_list_column(Index columnI Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -338,9 +349,13 @@ inline Intrusive_list_column::Intrusive_list_column(const Contain Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -370,9 +385,13 @@ inline Intrusive_list_column::Intrusive_list_column(Index columnI Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -445,7 +464,8 @@ inline Intrusive_list_column::Intrusive_list_column(Intrusive_lis operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)), column_(std::move(column.column_)) -{} +{ +} template inline Intrusive_list_column::~Intrusive_list_column() @@ -510,7 +530,7 @@ inline void Intrusive_list_column::reorder(const Row_index_map& v Entry* entry = &(*it); if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) @@ -555,10 +575,10 @@ inline typename Intrusive_list_column::ID_index Intrusive_list_co "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); return column_.back().get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -576,9 +596,9 @@ Intrusive_list_column::get_pivot_value() const if (column_.empty()) return 0; return column_.back().get_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry& entry : column_) { - if (entry.get_row_index() == Chain_opt::get_pivot()) return entry.get_element(); + if (entry.get_row_index() == Chain_opt::_get_pivot()) return entry.get_element(); } return Field_element(); // should never happen if chain column is used properly } @@ -641,8 +661,7 @@ Intrusive_list_column::rend() const noexcept template template -inline Intrusive_list_column& Intrusive_list_column::operator+=( - const Entry_range& column) +inline Intrusive_list_column& Intrusive_list_column::operator+=(const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -662,8 +681,8 @@ inline Intrusive_list_column& Intrusive_list_column& Intrusive_list_column -inline Intrusive_list_column& Intrusive_list_column::operator*=( - const Field_element& val) +inline Intrusive_list_column& Intrusive_list_column::operator*=(const Field_element& val) { if constexpr (Master_matrix::Option_list::is_z2) { if (val % 2 == 0) { @@ -710,7 +728,8 @@ inline Intrusive_list_column& Intrusive_list_column template inline Intrusive_list_column& Intrusive_list_column::multiply_target_and_add( - const Field_element& val, const Entry_range& column) + const Field_element& val, + const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -734,23 +753,24 @@ inline Intrusive_list_column& Intrusive_list_column inline Intrusive_list_column& Intrusive_list_column::multiply_target_and_add( - const Field_element& val, Intrusive_list_column& column) + const Field_element& val, + Intrusive_list_column& column) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -772,7 +792,8 @@ inline Intrusive_list_column& Intrusive_list_column template inline Intrusive_list_column& Intrusive_list_column::multiply_source_and_add( - const Entry_range& column, const Field_element& val) + const Entry_range& column, + const Field_element& val) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -793,21 +814,22 @@ inline Intrusive_list_column& Intrusive_list_column inline Intrusive_list_column& Intrusive_list_column::multiply_source_and_add( - Intrusive_list_column& column, const Field_element& val) + Intrusive_list_column& column, + const Field_element& val) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -828,6 +850,8 @@ inline void Intrusive_list_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_.end()); } else { @@ -841,6 +865,9 @@ inline Intrusive_list_column& Intrusive_list_column& Intrusive_list_column +inline Intrusive_list_column& Intrusive_list_column::operator=( + Intrusive_list_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + column_.clear_and_dispose(Delete_disposer(this)); + + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + column_ = std::move(other.column_); +} + template inline void Intrusive_list_column::_delete_entry(iterator& it) { @@ -861,10 +907,12 @@ inline void Intrusive_list_column::_delete_entry(iterator& it) template inline typename Intrusive_list_column::Entry* Intrusive_list_column::_insert_entry( - const Field_element& value, ID_index rowIndex, const iterator& position) + const Field_element& value, + ID_index rowIndex, + const iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_.insert(position, *newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -881,7 +929,7 @@ template inline void Intrusive_list_column::_insert_entry(ID_index rowIndex, const iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_.insert(position, *newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -926,7 +974,8 @@ inline bool Intrusive_list_column::_multiply_source_and_add(const */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_list_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_list_column& column) const + { return Gudhi::persistence_matrix::hash_column(column); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h index 17cb7d86..8da5d672 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/intrusive_set_column.h @@ -42,7 +42,6 @@ namespace persistence_matrix { * are stored uniquely in the underlying container. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Intrusive_set_column : public Master_matrix::Row_access_option, @@ -61,7 +60,8 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, private: using Field_operators = typename Master_matrix::Field_operators; using Column_support = - boost::intrusive::set, + boost::intrusive::set, boost::intrusive::base_hook >; using Entry_constructor = typename Master_matrix::Entry_constructor; @@ -80,10 +80,10 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Intrusive_set_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Intrusive_set_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Intrusive_set_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -98,11 +98,12 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -135,7 +136,8 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const Intrusive_set_column& c1, const Intrusive_set_column& c2) { + friend bool operator==(const Intrusive_set_column& c1, const Intrusive_set_column& c2) + { if (&c1 == &c2) return true; if constexpr (Master_matrix::Option_list::is_z2) { @@ -152,7 +154,9 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, return true; } } - friend bool operator<(const Intrusive_set_column& c1, const Intrusive_set_column& c2) { + + friend bool operator<(const Intrusive_set_column& c1, const Intrusive_set_column& c2) + { if (&c1 == &c2) return false; if constexpr (Master_matrix::Option_list::is_z2) { @@ -172,8 +176,10 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, // Disabled with row access. Intrusive_set_column& operator=(const Intrusive_set_column& other); + Intrusive_set_column& operator=(Intrusive_set_column&& other) noexcept; - friend void swap(Intrusive_set_column& col1, Intrusive_set_column& col2) { + friend void swap(Intrusive_set_column& col1, Intrusive_set_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -201,10 +207,11 @@ class Intrusive_set_column : public Master_matrix::Row_access_option, // The disposer object function for boost intrusive container struct Delete_disposer { - Delete_disposer() {}; + Delete_disposer() = default; Delete_disposer(Intrusive_set_column* col) : col_(col) {}; - void operator()(Entry* delete_this) { + void operator()(Entry* delete_this) + { if constexpr (Master_matrix::Option_list::has_row_access) col_->unlink(delete_this); col_->entryPool_->destroy(delete_this); } @@ -263,7 +270,8 @@ inline Intrusive_set_column::Intrusive_set_column(Column_settings operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -304,9 +312,13 @@ inline Intrusive_set_column::Intrusive_set_column(Index columnInd Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -336,9 +348,13 @@ inline Intrusive_set_column::Intrusive_set_column(const Container Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -367,9 +383,13 @@ inline Intrusive_set_column::Intrusive_set_column(Index columnInd Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -440,7 +460,8 @@ inline Intrusive_set_column::Intrusive_set_column(Intrusive_set_c column_(std::move(column.column_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template inline Intrusive_set_column::~Intrusive_set_column() @@ -489,7 +510,8 @@ inline std::size_t Intrusive_set_column::size() const template template -inline void Intrusive_set_column::reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex) +inline void Intrusive_set_column::reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex) { static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, "Method not available for chain columns."); @@ -499,12 +521,13 @@ inline void Intrusive_set_column::reorder(const Row_index_map& va if constexpr (Master_matrix::Option_list::has_row_access) { for (auto it = column_.begin(); it != column_.end();) { Entry* newEntry = entryPool_->construct( - columnIndex == static_cast(-1) ? RA_opt::columnIndex_ : columnIndex, valueMap.at(it->get_row_index())); + columnIndex == Master_matrix::template get_null_value() ? RA_opt::get_column_index() : columnIndex, + valueMap.at(it->get_row_index())); if constexpr (!Master_matrix::Option_list::is_z2) { newEntry->set_element(it->get_element()); } newSet.insert(newSet.end(), *newEntry); - _delete_entry(it); // increases it + _delete_entry(it); // increases it if constexpr (Master_matrix::Option_list::has_intrusive_rows) // intrusive list RA_opt::insert_entry(newEntry->get_row_index(), newEntry); } @@ -557,10 +580,10 @@ inline typename Intrusive_set_column::ID_index Intrusive_set_colu "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); return column_.rbegin()->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -578,8 +601,8 @@ Intrusive_set_column::get_pivot_value() const if (column_.empty()) return 0; return column_.rbegin()->get_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return 0; - auto it = column_.find(Entry(Chain_opt::get_pivot())); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return 0; + auto it = column_.find(Entry(Chain_opt::_get_pivot())); GUDHI_CHECK(it != column_.end(), "Intrusive_set_column::get_pivot_value - Pivot not found only if the column was misused."); return it->get_element(); @@ -663,8 +686,8 @@ inline Intrusive_set_column& Intrusive_set_column: if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -710,7 +733,8 @@ inline Intrusive_set_column& Intrusive_set_column: template template inline Intrusive_set_column& Intrusive_set_column::multiply_target_and_add( - const Field_element& val, const Entry_range& column) + const Field_element& val, + const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -734,23 +758,24 @@ inline Intrusive_set_column& Intrusive_set_column: template inline Intrusive_set_column& Intrusive_set_column::multiply_target_and_add( - const Field_element& val, Intrusive_set_column& column) + const Field_element& val, + Intrusive_set_column& column) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -772,7 +797,8 @@ inline Intrusive_set_column& Intrusive_set_column: template template inline Intrusive_set_column& Intrusive_set_column::multiply_source_and_add( - const Entry_range& column, const Field_element& val) + const Entry_range& column, + const Field_element& val) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -793,21 +819,22 @@ inline Intrusive_set_column& Intrusive_set_column: template inline Intrusive_set_column& Intrusive_set_column::multiply_source_and_add( - Intrusive_set_column& column, const Field_element& val) + Intrusive_set_column& column, + const Field_element& val) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -828,6 +855,8 @@ inline void Intrusive_set_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_.end()); } else { @@ -841,6 +870,9 @@ inline Intrusive_set_column& Intrusive_set_column: { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // otherwise the column will be destroyed before copying itself... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -853,6 +885,27 @@ inline Intrusive_set_column& Intrusive_set_column: return *this; } +template +inline Intrusive_set_column& Intrusive_set_column::operator=( + Intrusive_set_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + column_.clear_and_dispose(Delete_disposer(this)); + + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + column_ = std::move(other.column_); + + return *this; +} + template inline void Intrusive_set_column::_delete_entry(iterator& it) { @@ -861,10 +914,12 @@ inline void Intrusive_set_column::_delete_entry(iterator& it) template inline typename Intrusive_set_column::Entry* Intrusive_set_column::_insert_entry( - const Field_element& value, ID_index rowIndex, const iterator& position) + const Field_element& value, + ID_index rowIndex, + const iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_.insert(position, *newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -881,7 +936,7 @@ template inline void Intrusive_set_column::_insert_entry(ID_index rowIndex, const iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_.insert(position, *newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -926,7 +981,8 @@ inline bool Intrusive_set_column::_multiply_source_and_add(const */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_set_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_set_column& column) const + { return Gudhi::persistence_matrix::hash_column(column); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h index 5793d523..8d37d5b2 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/list_column.h @@ -42,7 +42,6 @@ namespace persistence_matrix { * are stored uniquely in the underlying container. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class List_column : public Master_matrix::Row_access_option, @@ -78,10 +77,10 @@ class List_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - List_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + List_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template List_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -96,11 +95,12 @@ class List_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -133,7 +133,8 @@ class List_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const List_column& c1, const List_column& c2) { + friend bool operator==(const List_column& c1, const List_column& c2) + { if (&c1 == &c2) return true; auto it1 = c1.column_.begin(); @@ -151,7 +152,9 @@ class List_column : public Master_matrix::Row_access_option, } return true; } - friend bool operator<(const List_column& c1, const List_column& c2) { + + friend bool operator<(const List_column& c1, const List_column& c2) + { if (&c1 == &c2) return false; auto it1 = c1.column_.begin(); @@ -169,8 +172,10 @@ class List_column : public Master_matrix::Row_access_option, // Disabled with row access. List_column& operator=(const List_column& other); + List_column& operator=(List_column&& other) noexcept; - friend void swap(List_column& col1, List_column& col2) { + friend void swap(List_column& col1, List_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -220,7 +225,9 @@ class List_column : public Master_matrix::Row_access_option, Column& targetColumn); void _delete_entry(typename Column_support::iterator& it); - Entry* _insert_entry(const Field_element& value, ID_index rowIndex, const typename Column_support::iterator& position); + Entry* _insert_entry(const Field_element& value, + ID_index rowIndex, + const typename Column_support::iterator& position); void _insert_entry(ID_index rowIndex, const typename Column_support::iterator& position); void _update_entry(const Field_element& value, ID_index rowIndex, const typename Column_support::iterator& position); void _update_entry(ID_index rowIndex, const typename Column_support::iterator& position); @@ -240,7 +247,8 @@ inline List_column::List_column(Column_settings* colSettings) operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -282,9 +290,13 @@ inline List_column::List_column(Index columnIndex, Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -316,9 +328,13 @@ inline List_column::List_column(const Container& nonZeroRowIndice Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -349,9 +365,13 @@ inline List_column::List_column(Index columnIndex, Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -433,10 +453,12 @@ inline List_column::List_column(List_column&& column) noexcept column_(std::move(column.column_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template -inline List_column::~List_column() { +inline List_column::~List_column() +{ for (auto* entry : column_) { if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(entry); entryPool_->destroy(entry); @@ -499,7 +521,7 @@ inline void List_column::reorder(const Row_index_map& valueMap, [ Entry* entry = *it; if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) @@ -549,10 +571,10 @@ inline typename List_column::ID_index List_column: "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); return column_.back()->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -569,9 +591,9 @@ inline typename List_column::Field_element List_columnget_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); + if (entry->get_row_index() == Chain_opt::_get_pivot()) return entry->get_element(); } return Field_element(); // should never happen if chain column is used properly } @@ -647,8 +669,8 @@ inline List_column& List_column::operator+=(List_c if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -671,7 +693,7 @@ inline List_column& List_column::operator*=(unsign } else { Field_element val = operators_->get_value(v); - if (val == 0u) { + if (val == 0U) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); } else { @@ -680,7 +702,7 @@ inline List_column& List_column::operator*=(unsign return *this; } - if (val == 1u) return *this; + if (val == 1U) return *this; for (Entry* entry : column_) { operators_->multiply_inplace(entry->get_element(), val); @@ -725,16 +747,16 @@ inline List_column& List_column::multiply_target_a if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -784,14 +806,14 @@ inline List_column& List_column::multiply_source_a if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -812,6 +834,8 @@ inline void List_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_.end()); } else { @@ -824,6 +848,9 @@ inline List_column& List_column::operator=(const L { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -832,7 +859,6 @@ inline List_column& List_column::operator=(const L while (column_.size() > other.column_.size()) { if (column_.back() != nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(column_.back()); tmpPool->destroy(column_.back()); } column_.pop_back(); @@ -842,7 +868,6 @@ inline List_column& List_column::operator=(const L auto it = column_.begin(); for (const Entry* entry : other.column_) { if (*it != nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(*it); tmpPool->destroy(*it); } if constexpr (Master_matrix::Option_list::is_z2) { @@ -857,6 +882,28 @@ inline List_column& List_column::operator=(const L return *this; } +template +inline List_column& List_column::operator=(List_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) entryPool_->destroy(entry); + } + + column_ = std::move(other.column_); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + template inline void List_column::_delete_entry(typename Column_support::iterator& it) { @@ -867,10 +914,12 @@ inline void List_column::_delete_entry(typename Column_support::i template inline typename List_column::Entry* List_column::_insert_entry( - const Field_element& value, ID_index rowIndex, const typename Column_support::iterator& position) + const Field_element& value, + ID_index rowIndex, + const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_.insert(position, newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -888,7 +937,7 @@ inline void List_column::_insert_entry(ID_index rowIndex, const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_.insert(position, newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -903,7 +952,7 @@ inline void List_column::_update_entry(const Field_element& value const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - *position = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + *position = entryPool_->construct(RA_opt::get_column_index(), rowIndex); (*position)->set_element(value); RA_opt::insert_entry(rowIndex, *position); } else { @@ -917,7 +966,7 @@ inline void List_column::_update_entry(ID_index rowIndex, const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - *position = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + *position = entryPool_->construct(RA_opt::get_column_index(), rowIndex); RA_opt::insert_entry(rowIndex, *position); } else { *position = entryPool_->construct(rowIndex); @@ -972,7 +1021,8 @@ inline bool List_column::_multiply_source_and_add(const Entry_ran */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::List_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::List_column& column) const + { return Gudhi::persistence_matrix::hash_column(column); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h index 21809f12..0ee3d7d2 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/naive_vector_column.h @@ -25,6 +25,7 @@ #include //std::swap, std::move & std::exchange #include +#include #include #include @@ -42,9 +43,8 @@ namespace persistence_matrix { * are stored uniquely in the underlying container. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ -template +template class Naive_vector_column : public Master_matrix::Row_access_option, public Master_matrix::Column_dimension_option, public Master_matrix::Chain_column_option @@ -60,7 +60,7 @@ class Naive_vector_column : public Master_matrix::Row_access_option, private: using Field_operators = typename Master_matrix::Field_operators; - using Column_support = std::vector; + using Column_support = Support; using Entry_constructor = typename Master_matrix::Entry_constructor; public: @@ -78,10 +78,10 @@ class Naive_vector_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Naive_vector_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Naive_vector_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Naive_vector_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -96,11 +96,12 @@ class Naive_vector_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -133,7 +134,8 @@ class Naive_vector_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const Naive_vector_column& c1, const Naive_vector_column& c2) { + friend bool operator==(const Naive_vector_column& c1, const Naive_vector_column& c2) + { if (&c1 == &c2) return true; if (c1.column_.size() != c2.column_.size()) return false; @@ -151,7 +153,9 @@ class Naive_vector_column : public Master_matrix::Row_access_option, } return true; } - friend bool operator<(const Naive_vector_column& c1, const Naive_vector_column& c2) { + + friend bool operator<(const Naive_vector_column& c1, const Naive_vector_column& c2) + { if (&c1 == &c2) return false; auto it1 = c1.column_.begin(); @@ -169,8 +173,10 @@ class Naive_vector_column : public Master_matrix::Row_access_option, // Disabled with row access. Naive_vector_column& operator=(const Naive_vector_column& other); + Naive_vector_column& operator=(Naive_vector_column&& other) noexcept; - friend void swap(Naive_vector_column& col1, Naive_vector_column& col2) { + friend void swap(Naive_vector_column& col1, Naive_vector_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -224,23 +230,30 @@ class Naive_vector_column : public Master_matrix::Row_access_option, }; template -inline Naive_vector_column::Naive_vector_column(Column_settings* colSettings) +using Naive_std_vector_column = Naive_vector_column >; +template +using Naive_small_vector_column = + Naive_vector_column >; + +template +inline Naive_vector_column::Naive_vector_column(Column_settings* colSettings) : RA_opt(), Dim_opt(), Chain_opt(), operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } } -template +template template -inline Naive_vector_column::Naive_vector_column(const Container& nonZeroRowIndices, - Column_settings* colSettings) +inline Naive_vector_column::Naive_vector_column(const Container& nonZeroRowIndices, + Column_settings* colSettings) : RA_opt(), Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt(), @@ -264,19 +277,23 @@ inline Naive_vector_column::Naive_vector_column(const Container& } } -template +template template -inline Naive_vector_column::Naive_vector_column(Index columnIndex, - const Container& nonZeroRowIndices, - Row_container* rowContainer, - Column_settings* colSettings) +inline Naive_vector_column::Naive_vector_column(Index columnIndex, + const Container& nonZeroRowIndices, + Row_container* rowContainer, + Column_settings* colSettings) : RA_opt(columnIndex, rowContainer), Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -299,18 +316,22 @@ inline Naive_vector_column::Naive_vector_column(Index columnIndex } } -template +template template -inline Naive_vector_column::Naive_vector_column(const Container& nonZeroRowIndices, - Dimension dimension, - Column_settings* colSettings) +inline Naive_vector_column::Naive_vector_column(const Container& nonZeroRowIndices, + Dimension dimension, + Column_settings* colSettings) : RA_opt(), Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -330,20 +351,24 @@ inline Naive_vector_column::Naive_vector_column(const Container& } } -template +template template -inline Naive_vector_column::Naive_vector_column(Index columnIndex, - const Container& nonZeroRowIndices, - Dimension dimension, - Row_container* rowContainer, - Column_settings* colSettings) +inline Naive_vector_column::Naive_vector_column(Index columnIndex, + const Container& nonZeroRowIndices, + Dimension dimension, + Row_container* rowContainer, + Column_settings* colSettings) : RA_opt(columnIndex, rowContainer), Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -363,9 +388,9 @@ inline Naive_vector_column::Naive_vector_column(Index columnIndex } } -template -inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, - Column_settings* colSettings) +template +inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, + Column_settings* colSettings) : RA_opt(), Dim_opt(static_cast(column)), Chain_opt(static_cast(column)), @@ -391,12 +416,12 @@ inline Naive_vector_column::Naive_vector_column(const Naive_vecto } } -template +template template -inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, - Index columnIndex, - Row_container* rowContainer, - Column_settings* colSettings) +inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, + Index columnIndex, + Row_container* rowContainer, + Column_settings* colSettings) : RA_opt(columnIndex, rowContainer), Dim_opt(static_cast(column)), Chain_opt(static_cast(column)), @@ -418,27 +443,28 @@ inline Naive_vector_column::Naive_vector_column(const Naive_vecto } } -template -inline Naive_vector_column::Naive_vector_column(Naive_vector_column&& column) noexcept +template +inline Naive_vector_column::Naive_vector_column(Naive_vector_column&& column) noexcept : RA_opt(std::move(static_cast(column))), Dim_opt(std::move(static_cast(column))), Chain_opt(std::move(static_cast(column))), column_(std::move(column.column_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} -template -inline Naive_vector_column::~Naive_vector_column() +template +inline Naive_vector_column::~Naive_vector_column() { for (auto* entry : column_) { _delete_entry(entry); } } -template -inline std::vector::Field_element> -Naive_vector_column::get_content(int columnLength) const +template +inline std::vector::Field_element> +Naive_vector_column::get_content(int columnLength) const { if (columnLength < 0 && column_.size() > 0) columnLength = column_.back()->get_row_index() + 1; @@ -457,30 +483,31 @@ Naive_vector_column::get_content(int columnLength) const return container; } -template -inline bool Naive_vector_column::is_non_zero(ID_index rowIndex) const +template +inline bool Naive_vector_column::is_non_zero(ID_index rowIndex) const { Entry entry(rowIndex); - return std::binary_search(column_.begin(), column_.end(), &entry, - [](const Entry* a, const Entry* b) { return a->get_row_index() < b->get_row_index(); }); + return std::binary_search(column_.begin(), column_.end(), &entry, [](const Entry* a, const Entry* b) { + return a->get_row_index() < b->get_row_index(); + }); } -template -inline bool Naive_vector_column::is_empty() const +template +inline bool Naive_vector_column::is_empty() const { return column_.empty(); } -template -inline std::size_t Naive_vector_column::size() const +template +inline std::size_t Naive_vector_column::size() const { return column_.size(); } -template +template template -inline void Naive_vector_column::reorder(const Row_index_map& valueMap, - [[maybe_unused]] Index columnIndex) +inline void Naive_vector_column::reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex) { static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, "Method not available for chain columns."); @@ -488,7 +515,7 @@ inline void Naive_vector_column::reorder(const Row_index_map& val for (Entry* entry : column_) { if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) @@ -505,8 +532,8 @@ inline void Naive_vector_column::reorder(const Row_index_map& val std::sort(column_.begin(), column_.end(), [](const Entry* c1, const Entry* c2) { return *c1 < *c2; }); } -template -inline void Naive_vector_column::clear() +template +inline void Naive_vector_column::clear() { static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, "Method not available for chain columns as a base element should not be empty."); @@ -519,8 +546,8 @@ inline void Naive_vector_column::clear() column_.clear(); } -template -inline void Naive_vector_column::clear(ID_index rowIndex) +template +inline void Naive_vector_column::clear(ID_index rowIndex) { static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, "Method not available for chain columns."); @@ -533,22 +560,23 @@ inline void Naive_vector_column::clear(ID_index rowIndex) } } -template -inline typename Naive_vector_column::ID_index Naive_vector_column::get_pivot() const +template +inline typename Naive_vector_column::ID_index +Naive_vector_column::get_pivot() const { static_assert(Master_matrix::isNonBasic, "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - return column_.empty() ? -1 : column_.back()->get_row_index(); + return column_.empty() ? Master_matrix::template get_null_value() : column_.back()->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } -template -inline typename Naive_vector_column::Field_element Naive_vector_column::get_pivot_value() - const +template +inline typename Naive_vector_column::Field_element +Naive_vector_column::get_pivot_value() const { static_assert(Master_matrix::isNonBasic, "Method not available for base columns."); // could technically be, but is the notion useful then? @@ -559,72 +587,75 @@ inline typename Naive_vector_column::Field_element Naive_vector_c if constexpr (Master_matrix::Option_list::is_of_boundary_type) { return column_.empty() ? Field_element() : column_.back()->get_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); + if (entry->get_row_index() == Chain_opt::_get_pivot()) return entry->get_element(); } return Field_element(); // should never happen if chain column is used properly } } } -template -inline typename Naive_vector_column::iterator Naive_vector_column::begin() noexcept +template +inline typename Naive_vector_column::iterator +Naive_vector_column::begin() noexcept { return column_.begin(); } -template -inline typename Naive_vector_column::const_iterator Naive_vector_column::begin() - const noexcept +template +inline typename Naive_vector_column::const_iterator +Naive_vector_column::begin() const noexcept { return column_.begin(); } -template -inline typename Naive_vector_column::iterator Naive_vector_column::end() noexcept +template +inline typename Naive_vector_column::iterator +Naive_vector_column::end() noexcept { return column_.end(); } -template -inline typename Naive_vector_column::const_iterator Naive_vector_column::end() - const noexcept +template +inline typename Naive_vector_column::const_iterator +Naive_vector_column::end() const noexcept { return column_.end(); } -template -inline typename Naive_vector_column::reverse_iterator -Naive_vector_column::rbegin() noexcept +template +inline typename Naive_vector_column::reverse_iterator +Naive_vector_column::rbegin() noexcept { return column_.rbegin(); } -template -inline typename Naive_vector_column::const_reverse_iterator Naive_vector_column::rbegin() - const noexcept +template +inline typename Naive_vector_column::const_reverse_iterator +Naive_vector_column::rbegin() const noexcept { return column_.rbegin(); } -template -inline typename Naive_vector_column::reverse_iterator -Naive_vector_column::rend() noexcept +template +inline typename Naive_vector_column::reverse_iterator +Naive_vector_column::rend() noexcept { return column_.rend(); } -template -inline typename Naive_vector_column::const_reverse_iterator Naive_vector_column::rend() - const noexcept +template +inline typename Naive_vector_column::const_reverse_iterator +Naive_vector_column::rend() const noexcept { return column_.rend(); } -template +template template -inline Naive_vector_column& Naive_vector_column::operator+=(const Entry_range& column) +inline Naive_vector_column& Naive_vector_column::operator+=( + const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -637,14 +668,15 @@ inline Naive_vector_column& Naive_vector_column::o return *this; } -template -inline Naive_vector_column& Naive_vector_column::operator+=(Naive_vector_column& column) +template +inline Naive_vector_column& Naive_vector_column::operator+=( + Naive_vector_column& column) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -653,8 +685,9 @@ inline Naive_vector_column& Naive_vector_column::o return *this; } -template -inline Naive_vector_column& Naive_vector_column::operator*=(unsigned int v) +template +inline Naive_vector_column& Naive_vector_column::operator*=( + unsigned int v) { if constexpr (Master_matrix::Option_list::is_z2) { if (v % 2 == 0) { @@ -687,10 +720,11 @@ inline Naive_vector_column& Naive_vector_column::o return *this; } -template +template template -inline Naive_vector_column& Naive_vector_column::multiply_target_and_add( - const Field_element& val, const Entry_range& column) +inline Naive_vector_column& +Naive_vector_column::multiply_target_and_add(const Field_element& val, + const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -712,25 +746,26 @@ inline Naive_vector_column& Naive_vector_column::m return *this; } -template -inline Naive_vector_column& Naive_vector_column::multiply_target_and_add( - const Field_element& val, Naive_vector_column& column) +template +inline Naive_vector_column& +Naive_vector_column::multiply_target_and_add(const Field_element& val, + Naive_vector_column& column) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -749,10 +784,11 @@ inline Naive_vector_column& Naive_vector_column::m return *this; } -template +template template -inline Naive_vector_column& Naive_vector_column::multiply_source_and_add( - const Entry_range& column, const Field_element& val) +inline Naive_vector_column& +Naive_vector_column::multiply_source_and_add(const Entry_range& column, + const Field_element& val) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -771,23 +807,24 @@ inline Naive_vector_column& Naive_vector_column::m return *this; } -template -inline Naive_vector_column& Naive_vector_column::multiply_source_and_add( - Naive_vector_column& column, const Field_element& val) +template +inline Naive_vector_column& +Naive_vector_column::multiply_source_and_add(Naive_vector_column& column, + const Field_element& val) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -803,11 +840,13 @@ inline Naive_vector_column& Naive_vector_column::m return *this; } -template -inline void Naive_vector_column::push_back(const Entry& entry) +template +inline void Naive_vector_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_); } else { @@ -815,12 +854,15 @@ inline void Naive_vector_column::push_back(const Entry& entry) } } -template -inline Naive_vector_column& Naive_vector_column::operator=( +template +inline Naive_vector_column& Naive_vector_column::operator=( const Naive_vector_column& other) { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -829,7 +871,6 @@ inline Naive_vector_column& Naive_vector_column::o while (column_.size() > other.column_.size()) { if (column_.back() == nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(column_.back()); tmpPool->destroy(column_.back()); } column_.pop_back(); @@ -839,7 +880,6 @@ inline Naive_vector_column& Naive_vector_column::o Index i = 0; for (const Entry* entry : other.column_) { if (column_[i] != nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(column_[i]); tmpPool->destroy(column_[i]); } if constexpr (Master_matrix::Option_list::is_z2) { @@ -854,26 +894,51 @@ inline Naive_vector_column& Naive_vector_column::o return *this; } -template -inline void Naive_vector_column::_delete_entry(Entry* entry) +template +inline Naive_vector_column& Naive_vector_column::operator=( + Naive_vector_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) _delete_entry(entry); + } + + column_ = std::move(other.column_); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + +template +inline void Naive_vector_column::_delete_entry(Entry* entry) { if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(entry); entryPool_->destroy(entry); } -template -inline void Naive_vector_column::_delete_entry(typename Column_support::iterator& it) +template +inline void Naive_vector_column::_delete_entry(typename Column_support::iterator& it) { _delete_entry(*it); ++it; } -template -inline typename Naive_vector_column::Entry* Naive_vector_column::_insert_entry( - const Field_element& value, ID_index rowIndex, Column_support& column) +template +inline typename Naive_vector_column::Entry* +Naive_vector_column::_insert_entry(const Field_element& value, + ID_index rowIndex, + Column_support& column) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column.push_back(newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -886,11 +951,11 @@ inline typename Naive_vector_column::Entry* Naive_vector_column -inline void Naive_vector_column::_insert_entry(ID_index rowIndex, Column_support& column) +template +inline void Naive_vector_column::_insert_entry(ID_index rowIndex, Column_support& column) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column.push_back(newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -898,13 +963,13 @@ inline void Naive_vector_column::_insert_entry(ID_index rowIndex, } } -template -inline void Naive_vector_column::_update_entry(const Field_element& value, - ID_index rowIndex, - Index position) +template +inline void Naive_vector_column::_update_entry(const Field_element& value, + ID_index rowIndex, + Index position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_[position] = newEntry; RA_opt::insert_entry(rowIndex, newEntry); @@ -914,11 +979,11 @@ inline void Naive_vector_column::_update_entry(const Field_elemen } } -template -inline void Naive_vector_column::_update_entry(ID_index rowIndex, Index position) +template +inline void Naive_vector_column::_update_entry(ID_index rowIndex, Index position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_[position] = newEntry; RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -926,9 +991,9 @@ inline void Naive_vector_column::_update_entry(ID_index rowIndex, } } -template +template template -inline bool Naive_vector_column::_add(const Entry_range& column) +inline bool Naive_vector_column::_add(const Entry_range& column) { if (column.begin() == column.end()) return false; if (column_.empty()) { // chain should never enter here. @@ -948,8 +1013,8 @@ inline bool Naive_vector_column::_add(const Entry_range& column) newColumn.reserve(column_.size() + column.size()); // safe upper bound auto pivotIsZeroed = _generic_add_to_column( - column, - *this, + column, + *this, [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, [&](typename Entry_range::const_iterator& itSource, [[maybe_unused]] const typename Column_support::iterator& itTarget) { @@ -976,12 +1041,12 @@ inline bool Naive_vector_column::_add(const Entry_range& column) return pivotIsZeroed; } -template +template template -inline bool Naive_vector_column::_multiply_target_and_add(const Field_element& val, - const Entry_range& column) +inline bool Naive_vector_column::_multiply_target_and_add(const Field_element& val, + const Entry_range& column) { - if (val == 0u) { + if (val == 0U) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); // this would not only mess up the base, but also the pivots stored. @@ -1034,12 +1099,12 @@ inline bool Naive_vector_column::_multiply_target_and_add(const F return pivotIsZeroed; } -template +template template -inline bool Naive_vector_column::_multiply_source_and_add(const Entry_range& column, - const Field_element& val) +inline bool Naive_vector_column::_multiply_source_and_add(const Entry_range& column, + const Field_element& val) { - if (val == 0u || column.begin() == column.end()) { + if (val == 0U || column.begin() == column.end()) { return false; } @@ -1082,9 +1147,10 @@ inline bool Naive_vector_column::_multiply_source_and_add(const E * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. * @tparam Entry_constructor Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. */ -template -struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Naive_vector_column& column) const { +template +struct std::hash > { + std::size_t operator()(const Gudhi::persistence_matrix::Naive_vector_column& column) const + { return Gudhi::persistence_matrix::hash_column(column); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h index 035ad0e1..f3cd461e 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/row_access.h @@ -29,13 +29,14 @@ namespace persistence_matrix { * @brief Empty structure. * Inherited instead of @ref Row_access, if the row access is not enabled. */ -struct Dummy_row_access -{ - friend void swap([[maybe_unused]] Dummy_row_access& d1, [[maybe_unused]] Dummy_row_access& d2) {} +struct Dummy_row_access { + friend void swap([[maybe_unused]] Dummy_row_access& d1, [[maybe_unused]] Dummy_row_access& d2) noexcept {} + + Dummy_row_access() = default; - Dummy_row_access() {} template - Dummy_row_access([[maybe_unused]] Index columnIndex, [[maybe_unused]] Row_container& rows) {} + Dummy_row_access([[maybe_unused]] Index columnIndex, [[maybe_unused]] Row_container& rows) + {} }; /** @@ -43,99 +44,109 @@ struct Dummy_row_access * @ingroup persistence_matrix * * @brief Class managing the row access for the columns. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class Row_access +class Row_access { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Matrix_entry = typename Master_matrix::Matrix_entry; /**< @ref Entry. */ - using Row_container = typename Master_matrix::Row_container; /**< Type of the row container. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Matrix_entry = typename Master_matrix::Matrix_entry; /**< @ref Entry. */ + using Row_container = typename Master_matrix::Row_container; /**< Type of the row container. */ /** - * @brief Default constructor. Sets the column index to -1 and the row container to nullptr. - * Should only be used by dummy columns. + * @brief Default constructor. Sets the column index to @ref Matrix::get_null_value "null index" and the row + * container to nullptr. Should only be used by dummy columns. */ Row_access(); /** * @brief Constructor setting the column index and the row container by the given values. - * + * * @param columnIndex Column index to store. * @param rows Pointer to the row container. */ Row_access(Index columnIndex, Row_container* rows); /** * @brief Move constructor. - * + * * @param other Column to move. */ Row_access(Row_access&& other) noexcept; + Row_access(const Row_access& other) = delete; + ~Row_access() = default; + /** * @brief Inserts the given entry at the given row index. - * + * * @param rowIndex @ref rowindex "Row index" of the entry. * @param entry Pointer to the entry to insert. */ void insert_entry(ID_index rowIndex, Matrix_entry* entry); + /** * @brief Removes the given entry from its row. - * + * * @param entry Pointer to the entry to remove. */ void unlink(Matrix_entry* entry); + /** - * @brief If @ref PersistenceMatrixOptions::has_intrusive_rows is false, updates the copy of the entry in its row. + * @brief If @ref PersistenceMatrixOptions::has_intrusive_rows is false, updates the copy of the entry in its row. * Otherwise does nothing. * * If the rows are intrusive, only a pointer of the entry is stored and therefore any update on the entry (value * or column index) is automatically forwarded. But for non intrusive rows, any update has to be pushed explicitly. - * + * * @param entry Entry to update. */ void update_entry(const Matrix_entry& entry); + /** * @brief Returns the @ref MatIdx column index. - * + * * @return The @ref MatIdx column index. */ Index get_column_index() const; + Row_access& operator=(const Row_access& other) = delete; + Row_access& operator=(Row_access&& other) noexcept = delete; + /** * @brief Swap operator. */ - friend void swap(Row_access& r1, Row_access& r2) { + friend void swap(Row_access& r1, Row_access& r2) noexcept + { std::swap(r1.rows_, r2.rows_); std::swap(r1.columnIndex_, r2.columnIndex_); } - protected: - Index columnIndex_; /**< Column index. */ - Row_container* rows_; /**< Row container. Be careful to not destroy before the columns. */ - private: + Index columnIndex_; /**< Column index. */ + Row_container* rows_; /**< Row container. Be careful to not destroy before the columns. */ + using Base_hook_matrix_row = typename Master_matrix::Base_hook_matrix_row; }; template -inline Row_access::Row_access() : columnIndex_(-1), rows_(nullptr) +inline Row_access::Row_access() + : columnIndex_(Master_matrix::template get_null_value()), rows_(nullptr) {} template inline Row_access::Row_access(Index columnIndex, Row_container* rows) - : columnIndex_(columnIndex), rows_(rows) + : columnIndex_(columnIndex), rows_(rows) {} template inline Row_access::Row_access(Row_access&& other) noexcept - : columnIndex_(std::exchange(other.columnIndex_, 0)), rows_(other.rows_) + : columnIndex_(std::exchange(other.columnIndex_, 0)), rows_(other.rows_) {} template -inline void Row_access::insert_entry(ID_index rowIndex, Matrix_entry* entry) +inline void Row_access::insert_entry(ID_index rowIndex, Matrix_entry* entry) { if (rows_ == nullptr) return; @@ -152,7 +163,7 @@ inline void Row_access::insert_entry(ID_index rowIndex, Matrix_en } template -inline void Row_access::unlink(Matrix_entry* entry) +inline void Row_access::unlink(Matrix_entry* entry) { if (rows_ == nullptr) return; @@ -169,7 +180,7 @@ inline void Row_access::unlink(Matrix_entry* entry) } template -inline void Row_access::update_entry(const Matrix_entry& entry) +inline void Row_access::update_entry(const Matrix_entry& entry) { if constexpr (!Master_matrix::Option_list::has_intrusive_rows) { if (rows_ == nullptr) return; @@ -181,7 +192,7 @@ inline void Row_access::update_entry(const Matrix_entry& entry) } template -inline typename Row_access::Index Row_access::get_column_index() const +inline typename Row_access::Index Row_access::get_column_index() const { return columnIndex_; } diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h index c2efb18b..39986ec3 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/set_column.h @@ -42,7 +42,6 @@ namespace persistence_matrix { * are stored uniquely in the underlying container. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Set_column : public Master_matrix::Row_access_option, @@ -83,10 +82,10 @@ class Set_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Set_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Set_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Set_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -101,11 +100,12 @@ class Set_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -138,7 +138,8 @@ class Set_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const Set_column& c1, const Set_column& c2) { + friend bool operator==(const Set_column& c1, const Set_column& c2) + { if (&c1 == &c2) return true; auto it1 = c1.column_.begin(); @@ -156,7 +157,9 @@ class Set_column : public Master_matrix::Row_access_option, } return true; } - friend bool operator<(const Set_column& c1, const Set_column& c2) { + + friend bool operator<(const Set_column& c1, const Set_column& c2) + { if (&c1 == &c2) return false; auto it1 = c1.column_.begin(); @@ -174,8 +177,10 @@ class Set_column : public Master_matrix::Row_access_option, // Disabled with row access. Set_column& operator=(const Set_column& other); + Set_column& operator=(Set_column&& other) noexcept; - friend void swap(Set_column& col1, Set_column& col2) { + friend void swap(Set_column& col1, Set_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -225,7 +230,9 @@ class Set_column : public Master_matrix::Row_access_option, Column& targetColumn); void _delete_entry(typename Column_support::iterator& it); - Entry* _insert_entry(const Field_element& value, ID_index rowIndex, const typename Column_support::iterator& position); + Entry* _insert_entry(const Field_element& value, + ID_index rowIndex, + const typename Column_support::iterator& position); void _insert_entry(ID_index rowIndex, const typename Column_support::iterator& position); template bool _add(const Entry_range& column); @@ -243,7 +250,8 @@ inline Set_column::Set_column(Column_settings* colSettings) operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -283,9 +291,13 @@ inline Set_column::Set_column(Index columnIndex, Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -315,9 +327,13 @@ inline Set_column::Set_column(const Container& nonZeroRowIndices, Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -346,9 +362,13 @@ inline Set_column::Set_column(Index columnIndex, Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), operators_(nullptr), @@ -424,7 +444,8 @@ inline Set_column::Set_column(Set_column&& column) noexcept column_(std::move(column.column_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template inline Set_column::~Set_column() @@ -487,7 +508,7 @@ inline void Set_column::reorder(const Row_index_map& valueMap, [[ for (Entry* entry : column_) { if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); newSet.insert(entry); @@ -541,10 +562,10 @@ inline typename Set_column::ID_index Set_column::g "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); return (*column_.rbegin())->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -561,9 +582,9 @@ inline typename Set_column::Field_element Set_columnget_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); + if (entry->get_row_index() == Chain_opt::_get_pivot()) return entry->get_element(); } return Field_element(); // should never happen if chain column is used properly } @@ -639,8 +660,8 @@ inline Set_column& Set_column::operator+=(Set_colu if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -717,16 +738,16 @@ inline Set_column& Set_column::multiply_target_and if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -776,14 +797,14 @@ inline Set_column& Set_column::multiply_source_and if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -804,6 +825,8 @@ inline void Set_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_.end()); } else { @@ -816,11 +839,13 @@ inline Set_column& Set_column::operator=(const Set { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); for (auto* entry : column_) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(entry); entryPool_->destroy(entry); } column_.clear(); @@ -839,6 +864,28 @@ inline Set_column& Set_column::operator=(const Set return *this; } +template +inline Set_column& Set_column::operator=(Set_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) entryPool_->destroy(entry); + } + + column_ = std::move(other.column_); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + template inline void Set_column::_delete_entry(typename Column_support::iterator& it) { @@ -849,10 +896,12 @@ inline void Set_column::_delete_entry(typename Column_support::it template inline typename Set_column::Entry* Set_column::_insert_entry( - const Field_element& value, ID_index rowIndex, const typename Column_support::iterator& position) + const Field_element& value, + ID_index rowIndex, + const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_.insert(position, newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -867,10 +916,10 @@ inline typename Set_column::Entry* Set_column::_in template inline void Set_column::_insert_entry(ID_index rowIndex, - const typename Column_support::iterator& position) + const typename Column_support::iterator& position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_.insert(position, newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -913,7 +962,8 @@ inline bool Set_column::_multiply_source_and_add(const Entry_rang */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Set_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::Set_column& column) const + { return Gudhi::persistence_matrix::hash_column(column); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h deleted file mode 100644 index 35bd6977..00000000 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/small_vector_column.h +++ /dev/null @@ -1,1093 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. - * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022-24 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -/** - * @file naive_vector_column.h - * @author Hannah Schreiber - * @brief Contains the @ref Gudhi::persistence_matrix::Naive_vector_column class. - * Also defines the std::hash method for @ref Gudhi::persistence_matrix::Naive_vector_column. - */ - -#ifndef PM_SMALL_VECTOR_COLUMN_H -#define PM_SMALL_VECTOR_COLUMN_H - -#include -#include -#include -#include //binary_search -#include //std::swap, std::move & std::exchange - -#include -#include - -#include -#include - -namespace Gudhi { -namespace persistence_matrix { - -/** - * @class Naive_vector_column naive_vector_column.h gudhi/Persistence_matrix/columns/naive_vector_column.h - * @ingroup persistence_matrix - * - * @brief Column class following the @ref PersistenceMatrixColumn concept. - * - * Column based on a vector structure. The entries are always ordered by row index and only non-zero values - * are stored uniquely in the underlying container. - * - * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. - */ -template -class Small_vector_column : public Master_matrix::Row_access_option, - public Master_matrix::Column_dimension_option, - public Master_matrix::Chain_column_option -{ - public: - using Master = Master_matrix; - using Index = typename Master_matrix::Index; - using ID_index = typename Master_matrix::ID_index; - using Dimension = typename Master_matrix::Dimension; - using Field_element = typename Master_matrix::Element; - using Entry = typename Master_matrix::Matrix_entry; - using Column_settings = typename Master_matrix::Column_settings; - - private: - using Field_operators = typename Master_matrix::Field_operators; - using Column_support = boost::container::small_vector; - using Entry_constructor = typename Master_matrix::Entry_constructor; - - public: - using iterator = boost::indirect_iterator; - using const_iterator = boost::indirect_iterator; - using reverse_iterator = boost::indirect_iterator; - using const_reverse_iterator = boost::indirect_iterator; - - Small_vector_column(Column_settings* colSettings = nullptr); - template - Small_vector_column(const Container& nonZeroRowIndices, Column_settings* colSettings); - template - Small_vector_column(Index columnIndex, - const Container& nonZeroRowIndices, - Row_container* rowContainer, - Column_settings* colSettings); - template - Small_vector_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); - template - Small_vector_column(Index columnIndex, - const Container& nonZeroChainRowIndices, - Dimension dimension, - Row_container* rowContainer, - Column_settings* colSettings); - Small_vector_column(const Small_vector_column& column, Column_settings* colSettings = nullptr); - template - Small_vector_column(const Small_vector_column& column, - Index columnIndex, - Row_container* rowContainer, - Column_settings* colSettings = nullptr); - Small_vector_column(Small_vector_column&& column) noexcept; - ~Small_vector_column(); - - std::vector get_content(int columnLength = -1) const; - bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; - - template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); - void clear(); - void clear(ID_index rowIndex); - - ID_index get_pivot() const; - Field_element get_pivot_value() const; - - iterator begin() noexcept; - const_iterator begin() const noexcept; - iterator end() noexcept; - const_iterator end() const noexcept; - reverse_iterator rbegin() noexcept; - const_reverse_iterator rbegin() const noexcept; - reverse_iterator rend() noexcept; - const_reverse_iterator rend() const noexcept; - - template - Small_vector_column& operator+=(const Entry_range& column); - Small_vector_column& operator+=(Small_vector_column& column); - - Small_vector_column& operator*=(unsigned int v); - - // this = v * this + column - template - Small_vector_column& multiply_target_and_add(const Field_element& val, const Entry_range& column); - Small_vector_column& multiply_target_and_add(const Field_element& val, Small_vector_column& column); - // this = this + column * v - template - Small_vector_column& multiply_source_and_add(const Entry_range& column, const Field_element& val); - Small_vector_column& multiply_source_and_add(Small_vector_column& column, const Field_element& val); - - void push_back(const Entry& entry); - - friend bool operator==(const Small_vector_column& c1, const Small_vector_column& c2) { - if (&c1 == &c2) return true; - if (c1.column_.size() != c2.column_.size()) return false; - - auto it1 = c1.column_.begin(); - auto it2 = c2.column_.begin(); - while (it1 != c1.column_.end() && it2 != c2.column_.end()) { - if constexpr (Master_matrix::Option_list::is_z2) { - if ((*it1)->get_row_index() != (*it2)->get_row_index()) return false; - } else { - if ((*it1)->get_row_index() != (*it2)->get_row_index() || (*it1)->get_element() != (*it2)->get_element()) - return false; - } - ++it1; - ++it2; - } - return true; - } - friend bool operator<(const Small_vector_column& c1, const Small_vector_column& c2) { - if (&c1 == &c2) return false; - - auto it1 = c1.column_.begin(); - auto it2 = c2.column_.begin(); - while (it1 != c1.column_.end() && it2 != c2.column_.end()) { - if ((*it1)->get_row_index() != (*it2)->get_row_index()) return (*it1)->get_row_index() < (*it2)->get_row_index(); - if constexpr (!Master_matrix::Option_list::is_z2) { - if ((*it1)->get_element() != (*it2)->get_element()) return (*it1)->get_element() < (*it2)->get_element(); - } - ++it1; - ++it2; - } - return it2 != c2.column_.end(); - } - - // Disabled with row access. - Small_vector_column& operator=(const Small_vector_column& other); - - friend void swap(Small_vector_column& col1, Small_vector_column& col2) { - swap(static_cast(col1), - static_cast(col2)); - swap(static_cast(col1), - static_cast(col2)); - swap(static_cast(col1), - static_cast(col2)); - col1.column_.swap(col2.column_); - std::swap(col1.operators_, col2.operators_); - std::swap(col1.entryPool_, col2.entryPool_); - } - - private: - using RA_opt = typename Master_matrix::Row_access_option; - using Dim_opt = typename Master_matrix::Column_dimension_option; - using Chain_opt = typename Master_matrix::Chain_column_option; - - Column_support column_; - Field_operators* operators_; - Entry_constructor* entryPool_; - - template - friend void _generic_merge_entry_to_column(Column& targetColumn, - Entry_iterator& itSource, - typename Column::Column_support::iterator& itTarget, - F1&& process_target, - F2&& process_source, - F3&& update_target1, - F4&& update_target2, - bool& pivotIsZeroed); - template - friend bool _generic_add_to_column(const Entry_range& source, - Column& targetColumn, - F1&& process_target, - F2&& process_source, - F3&& update_target1, - F4&& update_target2, - F5&& finish_target); - - void _delete_entry(Entry* entry); - void _delete_entry(typename Column_support::iterator& it); - Entry* _insert_entry(const Field_element& value, ID_index rowIndex, Column_support& column); - void _insert_entry(ID_index rowIndex, Column_support& column); - void _update_entry(const Field_element& value, ID_index rowIndex, Index position); - void _update_entry(ID_index rowIndex, Index position); - template - bool _add(const Entry_range& column); - template - bool _multiply_target_and_add(const Field_element& val, const Entry_range& column); - template - bool _multiply_source_and_add(const Entry_range& column, const Field_element& val); -}; - -template -inline Small_vector_column::Small_vector_column(Column_settings* colSettings) - : RA_opt(), - Dim_opt(), - Chain_opt(), - operators_(nullptr), - entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) -{ - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column - if constexpr (!Master_matrix::Option_list::is_z2) { - operators_ = &(colSettings->operators); - } -} - -template -template -inline Small_vector_column::Small_vector_column(const Container& nonZeroRowIndices, - Column_settings* colSettings) - : RA_opt(), - Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), - Chain_opt(), - column_(nonZeroRowIndices.size(), nullptr), - operators_(nullptr), - entryPool_(&(colSettings->entryConstructor)) -{ - static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, - "Constructor not available for chain columns, please specify the dimension of the chain."); - - Index i = 0; - if constexpr (Master_matrix::Option_list::is_z2) { - for (ID_index id : nonZeroRowIndices) { - _update_entry(id, i++); - } - } else { - operators_ = &(colSettings->operators); - for (const auto& p : nonZeroRowIndices) { - _update_entry(operators_->get_value(p.second), p.first, i++); - } - } -} - -template -template -inline Small_vector_column::Small_vector_column(Index columnIndex, - const Container& nonZeroRowIndices, - Row_container* rowContainer, - Column_settings* colSettings) - : RA_opt(columnIndex, rowContainer), - Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), - Chain_opt([&] { - if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); - } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; - } - }()), - column_(nonZeroRowIndices.size(), nullptr), - operators_(nullptr), - entryPool_(&(colSettings->entryConstructor)) -{ - static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, - "Constructor not available for chain columns, please specify the dimension of the chain."); - - Index i = 0; - if constexpr (Master_matrix::Option_list::is_z2) { - for (ID_index id : nonZeroRowIndices) { - _update_entry(id, i++); - } - } else { - operators_ = &(colSettings->operators); - for (const auto& p : nonZeroRowIndices) { - _update_entry(operators_->get_value(p.second), p.first, i++); - } - } -} - -template -template -inline Small_vector_column::Small_vector_column(const Container& nonZeroRowIndices, - Dimension dimension, - Column_settings* colSettings) - : RA_opt(), - Dim_opt(dimension), - Chain_opt([&] { - if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); - } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; - } - }()), - column_(nonZeroRowIndices.size(), nullptr), - operators_(nullptr), - entryPool_(&(colSettings->entryConstructor)) -{ - Index i = 0; - if constexpr (Master_matrix::Option_list::is_z2) { - for (ID_index id : nonZeroRowIndices) { - _update_entry(id, i++); - } - } else { - operators_ = &(colSettings->operators); - for (const auto& p : nonZeroRowIndices) { - _update_entry(operators_->get_value(p.second), p.first, i++); - } - } -} - -template -template -inline Small_vector_column::Small_vector_column(Index columnIndex, - const Container& nonZeroRowIndices, - Dimension dimension, - Row_container* rowContainer, - Column_settings* colSettings) - : RA_opt(columnIndex, rowContainer), - Dim_opt(dimension), - Chain_opt([&] { - if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); - } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; - } - }()), - column_(nonZeroRowIndices.size(), nullptr), - operators_(nullptr), - entryPool_(&(colSettings->entryConstructor)) -{ - Index i = 0; - if constexpr (Master_matrix::Option_list::is_z2) { - for (ID_index id : nonZeroRowIndices) { - _update_entry(id, i++); - } - } else { - operators_ = &(colSettings->operators); - for (const auto& p : nonZeroRowIndices) { - _update_entry(operators_->get_value(p.second), p.first, i++); - } - } -} - -template -inline Small_vector_column::Small_vector_column(const Small_vector_column& column, - Column_settings* colSettings) - : RA_opt(), - Dim_opt(static_cast(column)), - Chain_opt(static_cast(column)), - column_(column.column_.size(), nullptr), - operators_(colSettings == nullptr ? column.operators_ : nullptr), - entryPool_(colSettings == nullptr ? column.entryPool_ : &(colSettings->entryConstructor)) -{ - static_assert(!Master_matrix::Option_list::has_row_access, - "Simple copy constructor not available when row access option enabled. Please specify the new column " - "index and the row container."); - - if constexpr (!Master_matrix::Option_list::is_z2) { - if (colSettings != nullptr) operators_ = &(colSettings->operators); - } - - Index i = 0; - for (const Entry* entry : column.column_) { - if constexpr (Master_matrix::Option_list::is_z2) { - _update_entry(entry->get_row_index(), i++); - } else { - _update_entry(entry->get_element(), entry->get_row_index(), i++); - } - } -} - -template -template -inline Small_vector_column::Small_vector_column(const Small_vector_column& column, - Index columnIndex, - Row_container* rowContainer, - Column_settings* colSettings) - : RA_opt(columnIndex, rowContainer), - Dim_opt(static_cast(column)), - Chain_opt(static_cast(column)), - column_(column.column_.size(), nullptr), - operators_(colSettings == nullptr ? column.operators_ : nullptr), - entryPool_(colSettings == nullptr ? column.entryPool_ : &(colSettings->entryConstructor)) -{ - if constexpr (!Master_matrix::Option_list::is_z2) { - if (colSettings != nullptr) operators_ = &(colSettings->operators); - } - - Index i = 0; - for (const Entry* entry : column.column_) { - if constexpr (Master_matrix::Option_list::is_z2) { - _update_entry(entry->get_row_index(), i++); - } else { - _update_entry(entry->get_element(), entry->get_row_index(), i++); - } - } -} - -template -inline Small_vector_column::Small_vector_column(Small_vector_column&& column) noexcept - : RA_opt(std::move(static_cast(column))), - Dim_opt(std::move(static_cast(column))), - Chain_opt(std::move(static_cast(column))), - column_(std::move(column.column_)), - operators_(std::exchange(column.operators_, nullptr)), - entryPool_(std::exchange(column.entryPool_, nullptr)) -{} - -template -inline Small_vector_column::~Small_vector_column() -{ - for (auto* entry : column_) { - _delete_entry(entry); - } -} - -template -inline std::vector::Field_element> -Small_vector_column::get_content(int columnLength) const -{ - if (columnLength < 0 && column_.size() > 0) - columnLength = column_.back()->get_row_index() + 1; - else if (columnLength < 0) - return std::vector(); - - std::vector container(columnLength, 0); - for (auto it = column_.begin(); it != column_.end() && (*it)->get_row_index() < static_cast(columnLength); - ++it) { - if constexpr (Master_matrix::Option_list::is_z2) { - container[(*it)->get_row_index()] = 1; - } else { - container[(*it)->get_row_index()] = (*it)->get_element(); - } - } - return container; -} - -template -inline bool Small_vector_column::is_non_zero(ID_index rowIndex) const -{ - Entry entry(rowIndex); - return std::binary_search(column_.begin(), column_.end(), &entry, - [](const Entry* a, const Entry* b) { return a->get_row_index() < b->get_row_index(); }); -} - -template -inline bool Small_vector_column::is_empty() const -{ - return column_.empty(); -} - -template -inline std::size_t Small_vector_column::size() const -{ - return column_.size(); -} - -template -template -inline void Small_vector_column::reorder(const Row_index_map& valueMap, - [[maybe_unused]] Index columnIndex) -{ - static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, - "Method not available for chain columns."); - - for (Entry* entry : column_) { - if constexpr (Master_matrix::Option_list::has_row_access) { - RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); - } - entry->set_row_index(valueMap.at(entry->get_row_index())); - if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) - RA_opt::insert_entry(entry->get_row_index(), entry); - } - - // all entries have to be deleted first, to avoid problem with insertion when row is a set - if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { - for (Entry* entry : column_) { - RA_opt::insert_entry(entry->get_row_index(), entry); - } - } - - std::sort(column_.begin(), column_.end(), [](const Entry* c1, const Entry* c2) { return *c1 < *c2; }); -} - -template -inline void Small_vector_column::clear() -{ - static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, - "Method not available for chain columns as a base element should not be empty."); - - for (auto* entry : column_) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(entry); - entryPool_->destroy(entry); - } - - column_.clear(); -} - -template -inline void Small_vector_column::clear(ID_index rowIndex) -{ - static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, - "Method not available for chain columns."); - - auto it = column_.begin(); - while (it != column_.end() && (*it)->get_row_index() != rowIndex) ++it; - if (it != column_.end()) { - _delete_entry(*it); - column_.erase(it); - } -} - -template -inline typename Small_vector_column::ID_index Small_vector_column::get_pivot() const -{ - static_assert(Master_matrix::isNonBasic, - "Method not available for base columns."); // could technically be, but is the notion useful then? - - if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - return column_.empty() ? -1 : column_.back()->get_row_index(); - } else { - return Chain_opt::get_pivot(); - } -} - -template -inline typename Small_vector_column::Field_element Small_vector_column::get_pivot_value() - const -{ - static_assert(Master_matrix::isNonBasic, - "Method not available for base columns."); // could technically be, but is the notion useful then? - - if constexpr (Master_matrix::Option_list::is_z2) { - return 1; - } else { - if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - return column_.empty() ? Field_element() : column_.back()->get_element(); - } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); - for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); - } - return Field_element(); // should never happen if chain column is used properly - } - } -} - -template -inline typename Small_vector_column::iterator Small_vector_column::begin() noexcept -{ - return column_.begin(); -} - -template -inline typename Small_vector_column::const_iterator Small_vector_column::begin() - const noexcept -{ - return column_.begin(); -} - -template -inline typename Small_vector_column::iterator Small_vector_column::end() noexcept -{ - return column_.end(); -} - -template -inline typename Small_vector_column::const_iterator Small_vector_column::end() - const noexcept -{ - return column_.end(); -} - -template -inline typename Small_vector_column::reverse_iterator -Small_vector_column::rbegin() noexcept -{ - return column_.rbegin(); -} - -template -inline typename Small_vector_column::const_reverse_iterator Small_vector_column::rbegin() - const noexcept -{ - return column_.rbegin(); -} - -template -inline typename Small_vector_column::reverse_iterator -Small_vector_column::rend() noexcept -{ - return column_.rend(); -} - -template -inline typename Small_vector_column::const_reverse_iterator Small_vector_column::rend() - const noexcept -{ - return column_.rend(); -} - -template -template -inline Small_vector_column& Small_vector_column::operator+=(const Entry_range& column) -{ - static_assert((!Master_matrix::isNonBasic || std::is_same_v), - "For boundary columns, the range has to be a column of same type to help ensure the validity of the " - "base element."); // could be removed, if we give the responsibility to the user. - static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), - "For chain columns, the given column cannot be constant."); - - _add(column); - - return *this; -} - -template -inline Small_vector_column& Small_vector_column::operator+=(Small_vector_column& column) -{ - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - // assumes that the addition never zeros out this column. - if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); - } - } else { - _add(column); - } - - return *this; -} - -template -inline Small_vector_column& Small_vector_column::operator*=(unsigned int v) -{ - if constexpr (Master_matrix::Option_list::is_z2) { - if (v % 2 == 0) { - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - throw std::invalid_argument("A chain column should not be multiplied by 0."); - } else { - clear(); - } - } - } else { - Field_element val = operators_->get_value(v); - - if (val == Field_operators::get_additive_identity()) { - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - throw std::invalid_argument("A chain column should not be multiplied by 0."); - } else { - clear(); - } - return *this; - } - - if (val == Field_operators::get_multiplicative_identity()) return *this; - - for (Entry* entry : column_) { - operators_->multiply_inplace(entry->get_element(), val); - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::update_entry(*entry); - } - } - - return *this; -} - -template -template -inline Small_vector_column& Small_vector_column::multiply_target_and_add( - const Field_element& val, const Entry_range& column) -{ - static_assert((!Master_matrix::isNonBasic || std::is_same_v), - "For boundary columns, the range has to be a column of same type to help ensure the validity of the " - "base element."); // could be removed, if we give the responsibility to the user. - static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), - "For chain columns, the given column cannot be constant."); - - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - _add(column); - } else { - clear(); - _add(column); - } - } else { - _multiply_target_and_add(val, column); - } - - return *this; -} - -template -inline Small_vector_column& Small_vector_column::multiply_target_and_add( - const Field_element& val, Small_vector_column& column) -{ - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - // assumes that the addition never zeros out this column. - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); - } - } else { - throw std::invalid_argument("A chain column should not be multiplied by 0."); - } - } else { - if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); - } - } - } else { - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - _add(column); - } else { - clear(); - _add(column); - } - } else { - _multiply_target_and_add(val, column); - } - } - - return *this; -} - -template -template -inline Small_vector_column& Small_vector_column::multiply_source_and_add( - const Entry_range& column, const Field_element& val) -{ - static_assert((!Master_matrix::isNonBasic || std::is_same_v), - "For boundary columns, the range has to be a column of same type to help ensure the validity of the " - "base element."); // could be removed, if we give the responsibility to the user. - static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), - "For chain columns, the given column cannot be constant."); - - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - _add(column); - } - } else { - _multiply_source_and_add(column, val); - } - - return *this; -} - -template -inline Small_vector_column& Small_vector_column::multiply_source_and_add( - Small_vector_column& column, const Field_element& val) -{ - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - // assumes that the addition never zeros out this column. - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); - } - } - } else { - if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); - } - } - } else { - if constexpr (Master_matrix::Option_list::is_z2) { - if (val) { - _add(column); - } - } else { - _multiply_source_and_add(column, val); - } - } - - return *this; -} - -template -inline void Small_vector_column::push_back(const Entry& entry) -{ - static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); - - if constexpr (Master_matrix::Option_list::is_z2) { - _insert_entry(entry.get_row_index(), column_); - } else { - _insert_entry(entry.get_element(), entry.get_row_index(), column_); - } -} - -template -inline Small_vector_column& Small_vector_column::operator=( - const Small_vector_column& other) -{ - static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); - - Dim_opt::operator=(other); - Chain_opt::operator=(other); - - auto tmpPool = entryPool_; - entryPool_ = other.entryPool_; - - while (column_.size() > other.column_.size()) { - if (column_.back() == nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(column_.back()); - tmpPool->destroy(column_.back()); - } - column_.pop_back(); - } - - column_.resize(other.column_.size(), nullptr); - Index i = 0; - for (const Entry* entry : other.column_) { - if (column_[i] != nullptr) { - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(column_[i]); - tmpPool->destroy(column_[i]); - } - if constexpr (Master_matrix::Option_list::is_z2) { - _update_entry(entry->get_row_index(), i++); - } else { - _update_entry(entry->get_element(), entry->get_row_index(), i++); - } - } - - operators_ = other.operators_; - - return *this; -} - -template -inline void Small_vector_column::_delete_entry(Entry* entry) -{ - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::unlink(entry); - entryPool_->destroy(entry); -} - -template -inline void Small_vector_column::_delete_entry(typename Column_support::iterator& it) -{ - _delete_entry(*it); - ++it; -} - -template -inline typename Small_vector_column::Entry* Small_vector_column::_insert_entry( - const Field_element& value, ID_index rowIndex, Column_support& column) -{ - if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); - newEntry->set_element(value); - column.push_back(newEntry); - RA_opt::insert_entry(rowIndex, newEntry); - return newEntry; - } else { - Entry* newEntry = entryPool_->construct(rowIndex); - column.push_back(newEntry); - newEntry->set_element(value); - return newEntry; - } -} - -template -inline void Small_vector_column::_insert_entry(ID_index rowIndex, Column_support& column) -{ - if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); - column.push_back(newEntry); - RA_opt::insert_entry(rowIndex, newEntry); - } else { - column.push_back(entryPool_->construct(rowIndex)); - } -} - -template -inline void Small_vector_column::_update_entry(const Field_element& value, - ID_index rowIndex, - Index position) -{ - if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); - newEntry->set_element(value); - column_[position] = newEntry; - RA_opt::insert_entry(rowIndex, newEntry); - } else { - column_[position] = entryPool_->construct(rowIndex); - column_[position]->set_element(value); - } -} - -template -inline void Small_vector_column::_update_entry(ID_index rowIndex, Index position) -{ - if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); - column_[position] = newEntry; - RA_opt::insert_entry(rowIndex, newEntry); - } else { - column_[position] = entryPool_->construct(rowIndex); - } -} - -template -template -inline bool Small_vector_column::_add(const Entry_range& column) -{ - if (column.begin() == column.end()) return false; - if (column_.empty()) { // chain should never enter here. - column_.resize(column.size()); - Index i = 0; - for (const Entry& entry : column) { - if constexpr (Master_matrix::Option_list::is_z2) { - _update_entry(entry.get_row_index(), i++); - } else { - _update_entry(entry.get_element(), entry.get_row_index(), i++); - } - } - return true; - } - - Column_support newColumn; - newColumn.reserve(column_.size() + column.size()); // safe upper bound - - auto pivotIsZeroed = _generic_add_to_column( - column, - *this, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, - [&](typename Entry_range::const_iterator& itSource, - [[maybe_unused]] const typename Column_support::iterator& itTarget) { - if constexpr (Master_matrix::Option_list::is_z2) { - _insert_entry(itSource->get_row_index(), newColumn); - } else { - _insert_entry(itSource->get_element(), itSource->get_row_index(), newColumn); - } - }, - [&](Field_element& targetElement, typename Entry_range::const_iterator& itSource) { - if constexpr (!Master_matrix::Option_list::is_z2) - operators_->add_inplace(targetElement, itSource->get_element()); - }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, - [&](typename Column_support::iterator& itTarget) { - while (itTarget != column_.end()) { - newColumn.push_back(*itTarget); - itTarget++; - } - }); - - column_.swap(newColumn); - - return pivotIsZeroed; -} - -template -template -inline bool Small_vector_column::_multiply_target_and_add(const Field_element& val, - const Entry_range& column) -{ - if (val == 0u) { - if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - throw std::invalid_argument("A chain column should not be multiplied by 0."); - // this would not only mess up the base, but also the pivots stored. - } else { - clear(); - } - } - if (column_.empty()) { // chain should never enter here. - column_.resize(column.size()); - Index i = 0; - for (const Entry& entry : column) { - if constexpr (Master_matrix::Option_list::is_z2) { - _update_entry(entry.get_row_index(), i++); - } else { - _update_entry(entry.get_element(), entry.get_row_index(), i++); - } - } - return true; - } - - Column_support newColumn; - newColumn.reserve(column_.size() + column.size()); // safe upper bound - - auto pivotIsZeroed = _generic_add_to_column( - column, - *this, - [&](Entry* entryTarget) { - operators_->multiply_inplace(entryTarget->get_element(), val); - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::update_entry(*entryTarget); - newColumn.push_back(entryTarget); - }, - [&](typename Entry_range::const_iterator& itSource, const typename Column_support::iterator& itTarget) { - _insert_entry(itSource->get_element(), itSource->get_row_index(), newColumn); - }, - [&](Field_element& targetElement, typename Entry_range::const_iterator& itSource) { - operators_->multiply_and_add_inplace_front(targetElement, val, itSource->get_element()); - }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, - [&](typename Column_support::iterator& itTarget) { - while (itTarget != column_.end()) { - operators_->multiply_inplace((*itTarget)->get_element(), val); - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::update_entry(**itTarget); - newColumn.push_back(*itTarget); - itTarget++; - } - }); - - column_.swap(newColumn); - - return pivotIsZeroed; -} - -template -template -inline bool Small_vector_column::_multiply_source_and_add(const Entry_range& column, - const Field_element& val) -{ - if (val == 0u || column.begin() == column.end()) { - return false; - } - - Column_support newColumn; - newColumn.reserve(column_.size() + column.size()); // safe upper bound - - auto pivotIsZeroed = _generic_add_to_column( - column, - *this, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, - [&](typename Entry_range::const_iterator& itSource, const typename Column_support::iterator& itTarget) { - Entry* newEntry = _insert_entry(itSource->get_element(), itSource->get_row_index(), newColumn); - operators_->multiply_inplace(newEntry->get_element(), val); - if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::update_entry(*newEntry); - }, - [&](Field_element& targetElement, typename Entry_range::const_iterator& itSource) { - operators_->multiply_and_add_inplace_back(itSource->get_element(), val, targetElement); - }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }, - [&](typename Column_support::iterator& itTarget) { - while (itTarget != column_.end()) { - newColumn.push_back(*itTarget); - itTarget++; - } - }); - - column_.swap(newColumn); - - return pivotIsZeroed; -} - -} // namespace persistence_matrix -} // namespace Gudhi - -/** - * @ingroup persistence_matrix - * - * @brief Hash method for @ref Gudhi::persistence_matrix::Naive_vector_column. - * - * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. - * @tparam Entry_constructor Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. - */ -template -struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Small_vector_column& column) const { - return Gudhi::persistence_matrix::hash_column(column); - } -}; - -#endif // PM_NAIVE_VECTOR_COLUMN_H diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h index 9513a202..b48c76a5 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/unordered_set_column.h @@ -39,13 +39,12 @@ namespace persistence_matrix { // For unordered_set container. Outside of Unordered_set_column because of a msvc bug who can't compile properly // unordered_flat_set if the hash method is nested. template -struct EntryPointerHash -{ +struct EntryPointerHash { size_t operator()(const Entry* c) const { return std::hash()(*c); } }; + template -struct EntryPointerEq -{ +struct EntryPointerEq { bool operator()(const Entry* c1, const Entry* c2) const { return *c1 == *c2; } }; @@ -60,7 +59,6 @@ struct EntryPointerEq * also does not need to be ordered (contrary to most other column types). * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Unordered_set_column : public Master_matrix::Row_access_option, @@ -103,10 +101,10 @@ class Unordered_set_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Unordered_set_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Unordered_set_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Unordered_set_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -121,11 +119,12 @@ class Unordered_set_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); void clear(ID_index rowIndex); @@ -154,7 +153,8 @@ class Unordered_set_column : public Master_matrix::Row_access_option, void push_back(const Entry& entry); - friend bool operator==(const Unordered_set_column& c1, const Unordered_set_column& c2) { + friend bool operator==(const Unordered_set_column& c1, const Unordered_set_column& c2) + { if (&c1 == &c2) return true; if (c1.column_.size() != c2.column_.size()) return false; @@ -166,15 +166,14 @@ class Unordered_set_column : public Master_matrix::Row_access_option, } return true; } - friend bool operator<(const Unordered_set_column& c1, const Unordered_set_column& c2) { + + friend bool operator<(const Unordered_set_column& c1, const Unordered_set_column& c2) + { if (&c1 == &c2) return false; using ID_index = Unordered_set_column::ID_index; using Entry_rep = - typename std::conditional - >::type; + std::conditional_t>; auto it1 = c1.column_.begin(); auto it2 = c2.column_.begin(); @@ -211,8 +210,10 @@ class Unordered_set_column : public Master_matrix::Row_access_option, // Disabled with row access. Unordered_set_column& operator=(const Unordered_set_column& other); + Unordered_set_column& operator=(Unordered_set_column&& other) noexcept; - friend void swap(Unordered_set_column& col1, Unordered_set_column& col2) { + friend void swap(Unordered_set_column& col1, Unordered_set_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -254,7 +255,8 @@ inline Unordered_set_column::Unordered_set_column(Column_settings operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -296,9 +298,13 @@ inline Unordered_set_column::Unordered_set_column(Index columnInd Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -329,9 +335,13 @@ inline Unordered_set_column::Unordered_set_column(const Container Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -361,9 +371,13 @@ inline Unordered_set_column::Unordered_set_column(Index columnInd Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size()), @@ -443,7 +457,8 @@ inline Unordered_set_column::Unordered_set_column(Unordered_set_c column_(std::move(column.column_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template inline Unordered_set_column::~Unordered_set_column() @@ -508,7 +523,7 @@ inline void Unordered_set_column::reorder(const Row_index_map& va for (Entry* entry : column_) { if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); newSet.insert(entry); @@ -562,12 +577,12 @@ inline typename Unordered_set_column::ID_index Unordered_set_colu "Method not available for base columns."); // could technically be, but is the notion useful then? if constexpr (Master_matrix::Option_list::is_of_boundary_type) { - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); // linear search could be avoided with storing the pivot. But even then, some modifications of the column requires // the max, so not clear how much it is worth it. return (*std::max_element(column_.begin(), column_.end(), EntryPointerComp()))->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -585,9 +600,9 @@ Unordered_set_column::get_pivot_value() const if (column_.empty()) return 0; return (*std::max_element(column_.begin(), column_.end(), EntryPointerComp()))->get_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); + if (entry->get_row_index() == Chain_opt::_get_pivot()) return entry->get_element(); } return Field_element(); // should never happen if chain column is used properly } @@ -642,8 +657,8 @@ inline Unordered_set_column& Unordered_set_column: if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -689,7 +704,8 @@ inline Unordered_set_column& Unordered_set_column: template template inline Unordered_set_column& Unordered_set_column::multiply_target_and_add( - const Field_element& val, const Entry_range& column) + const Field_element& val, + const Entry_range& column) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -713,23 +729,24 @@ inline Unordered_set_column& Unordered_set_column: template inline Unordered_set_column& Unordered_set_column::multiply_target_and_add( - const Field_element& val, Unordered_set_column& column) + const Field_element& val, + Unordered_set_column& column) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -751,7 +768,8 @@ inline Unordered_set_column& Unordered_set_column: template template inline Unordered_set_column& Unordered_set_column::multiply_source_and_add( - const Entry_range& column, const Field_element& val) + const Entry_range& column, + const Field_element& val) { static_assert((!Master_matrix::isNonBasic || std::is_same_v), "For boundary columns, the range has to be a column of same type to help ensure the validity of the " @@ -772,21 +790,22 @@ inline Unordered_set_column& Unordered_set_column: template inline Unordered_set_column& Unordered_set_column::multiply_source_and_add( - Unordered_set_column& column, const Field_element& val) + Unordered_set_column& column, + const Field_element& val) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -807,6 +826,8 @@ inline void Unordered_set_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index()); } else { @@ -820,6 +841,9 @@ inline Unordered_set_column& Unordered_set_column: { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -843,6 +867,29 @@ inline Unordered_set_column& Unordered_set_column: return *this; } +template +inline Unordered_set_column& Unordered_set_column::operator=( + Unordered_set_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) entryPool_->destroy(entry); + } + + column_ = std::move(other.column_); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + template inline void Unordered_set_column::_delete_entry(typename Column_support::iterator& it) { @@ -855,10 +902,11 @@ inline void Unordered_set_column::_delete_entry(typename Column_s template inline typename Unordered_set_column::Entry* Unordered_set_column::_insert_entry( - const Field_element& value, ID_index rowIndex) + const Field_element& value, + ID_index rowIndex) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_.insert(newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -875,7 +923,7 @@ template inline void Unordered_set_column::_insert_entry(ID_index rowIndex) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_.insert(newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -904,7 +952,7 @@ template inline bool Unordered_set_column::_multiply_target_and_add(const Field_element& val, const Entry_range& column) { - if (val == 0u) { + if (val == 0U) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); // this would not only mess up the base, but also the pivots stored. @@ -928,7 +976,7 @@ template inline bool Unordered_set_column::_multiply_source_and_add(const Entry_range& column, const Field_element& val) { - if (val == 0u) { + if (val == 0U) { return false; } @@ -954,26 +1002,26 @@ inline bool Unordered_set_column::_generic_add(const Entry_range& for (const Entry& entry : source) { Entry* newEntry; if constexpr (Master_matrix::Option_list::has_row_access) { - newEntry = entryPool_->construct(RA_opt::columnIndex_, entry.get_row_index()); + newEntry = entryPool_->construct(RA_opt::get_column_index(), entry.get_row_index()); } else { newEntry = entryPool_->construct(entry.get_row_index()); } auto res = column_.insert(newEntry); if (res.second) { - process_source(entry, newEntry); + std::forward(process_source)(entry, newEntry); if constexpr (Master_matrix::Option_list::has_row_access) RA_opt::insert_entry(entry.get_row_index(), newEntry); } else { entryPool_->destroy(newEntry); if constexpr (Master_matrix::Option_list::is_z2) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if (entry.get_row_index() == Chain_opt::get_pivot()) pivotIsZeroed = true; + if (entry.get_row_index() == Chain_opt::_get_pivot()) pivotIsZeroed = true; } _delete_entry(res.first); } else { - update_target(*res.first, entry); + std::forward(update_target)(*res.first, entry); if ((*res.first)->get_element() == Field_operators::get_additive_identity()) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { - if ((*res.first)->get_row_index() == Chain_opt::get_pivot()) pivotIsZeroed = true; + if ((*res.first)->get_row_index() == Chain_opt::_get_pivot()) pivotIsZeroed = true; } _delete_entry(res.first); } else { @@ -999,7 +1047,8 @@ inline bool Unordered_set_column::_generic_add(const Entry_range& */ template struct std::hash> { - std::size_t operator()(const Gudhi::persistence_matrix::Unordered_set_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::Unordered_set_column& column) const + { // can't use Gudhi::persistence_matrix::hash_column because unordered std::size_t seed = 0; for (const auto& entry : column) { diff --git a/multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h b/multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h index 0721d86e..b73ce453 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/columns/vector_column.h @@ -27,6 +27,7 @@ #include //std::swap, std::move & std::exchange #include + #include namespace Gudhi { @@ -44,7 +45,6 @@ namespace persistence_matrix { * On the other hand, two entries will never have the same row index. * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. - * @tparam Entry_constructor Factory of @ref Entry classes. */ template class Vector_column : public Master_matrix::Row_access_option, @@ -80,10 +80,10 @@ class Vector_column : public Master_matrix::Row_access_option, Row_container* rowContainer, Column_settings* colSettings); template - Vector_column(const Container& nonZeroChainRowIndices, Dimension dimension, Column_settings* colSettings); + Vector_column(const Container& nonZeroRowIndices, Dimension dimension, Column_settings* colSettings); template Vector_column(Index columnIndex, - const Container& nonZeroChainRowIndices, + const Container& nonZeroRowIndices, Dimension dimension, Row_container* rowContainer, Column_settings* colSettings); @@ -98,11 +98,12 @@ class Vector_column : public Master_matrix::Row_access_option, std::vector get_content(int columnLength = -1) const; bool is_non_zero(ID_index rowIndex) const; - bool is_empty() const; - std::size_t size() const; + [[nodiscard]] bool is_empty() const; + [[nodiscard]] std::size_t size() const; template - void reorder(const Row_index_map& valueMap, [[maybe_unused]] Index columnIndex = -1); + void reorder(const Row_index_map& valueMap, + [[maybe_unused]] Index columnIndex = Master_matrix::template get_null_value()); void clear(); // do not clear an entry to 0 if the entry was already 0, otherwise size/is_empty will be wrong. void clear(ID_index rowIndex); @@ -138,7 +139,8 @@ class Vector_column : public Master_matrix::Row_access_option, std::size_t compute_hash_value(); - friend bool operator==(const Vector_column& c1, const Vector_column& c2) { + friend bool operator==(const Vector_column& c1, const Vector_column& c2) + { if (&c1 == &c2) return true; if (c1.erasedValues_.empty() && c2.erasedValues_.empty() && c1.column_.size() != c2.column_.size()) return false; @@ -170,7 +172,9 @@ class Vector_column : public Master_matrix::Row_access_option, return true; } } - friend bool operator<(const Vector_column& c1, const Vector_column& c2) { + + friend bool operator<(const Vector_column& c1, const Vector_column& c2) + { if (&c1 == &c2) return false; auto it1 = c1.column_.begin(); @@ -200,8 +204,10 @@ class Vector_column : public Master_matrix::Row_access_option, // Disabled with row access. Vector_column& operator=(const Vector_column& other); + Vector_column& operator=(Vector_column&& other) noexcept; - friend void swap(Vector_column& col1, Vector_column& col2) { + friend void swap(Vector_column& col1, Vector_column& col2) noexcept + { swap(static_cast(col1), static_cast(col2)); swap(static_cast(col1), @@ -220,20 +226,20 @@ class Vector_column : public Master_matrix::Row_access_option, using Chain_opt = typename Master_matrix::Chain_column_option; Column_support column_; - std::unordered_set erasedValues_; // TODO: test other containers? Useless when clear(Index) is never - // called, how much is it worth it? + // TODO: test other containers? Useless when clear(Index) is never called, how much is it worth it? + std::unordered_set erasedValues_; Field_operators* operators_; Entry_constructor* entryPool_; template friend void _generic_merge_entry_to_column(Column& targetColumn, - Entry_iterator& itSource, - typename Column::Column_support::iterator& itTarget, - F1&& process_target, - F2&& process_source, - F3&& update_target1, - F4&& update_target2, - bool& pivotIsZeroed); + Entry_iterator& itSource, + typename Column::Column_support::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); void _delete_entry(Entry* entry); void _delete_entry(typename Column_support::iterator& it); @@ -248,7 +254,7 @@ class Vector_column : public Master_matrix::Row_access_option, template bool _multiply_source_and_add(const Entry_range& column, const Field_element& val); template - bool _generic_add(const Entry_range& source, + bool _generic_add(const Entry_range& column, F1&& process_target, F2&& process_source, F3&& update_target1, @@ -263,7 +269,8 @@ inline Vector_column::Vector_column(Column_settings* colSettings) operators_(nullptr), entryPool_(colSettings == nullptr ? nullptr : &(colSettings->entryConstructor)) { - if (operators_ == nullptr && entryPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if (operators_ == nullptr && entryPool_ == nullptr) + return; // to allow default constructor which gives a dummy column if constexpr (!Master_matrix::Option_list::is_z2) { operators_ = &(colSettings->operators); } @@ -308,9 +315,13 @@ inline Vector_column::Vector_column(Index columnIndex, Dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -345,9 +356,13 @@ inline Vector_column::Vector_column(const Container& nonZeroRowIn Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -381,9 +396,13 @@ inline Vector_column::Vector_column(Index columnIndex, Dim_opt(dimension), Chain_opt([&] { if constexpr (Master_matrix::Option_list::is_z2) { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : *std::prev(nonZeroRowIndices.end()); } else { - return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() + ? Master_matrix::template get_null_value() + : std::prev(nonZeroRowIndices.end())->first; } }()), column_(nonZeroRowIndices.size(), nullptr), @@ -471,7 +490,8 @@ inline Vector_column::Vector_column(Vector_column&& column) noexc erasedValues_(std::move(column.erasedValues_)), operators_(std::exchange(column.operators_, nullptr)), entryPool_(std::exchange(column.entryPool_, nullptr)) -{} +{ +} template inline Vector_column::~Vector_column() @@ -512,8 +532,9 @@ inline bool Vector_column::is_non_zero(ID_index rowIndex) const if (erasedValues_.find(rowIndex) != erasedValues_.end()) return false; Entry entry(rowIndex); - return std::binary_search(column_.begin(), column_.end(), &entry, - [](const Entry* a, const Entry* b) { return a->get_row_index() < b->get_row_index(); }); + return std::binary_search(column_.begin(), column_.end(), &entry, [](const Entry* a, const Entry* b) { + return a->get_row_index() < b->get_row_index(); + }); } template @@ -549,7 +570,7 @@ inline void Vector_column::reorder(const Row_index_map& valueMap, for (Entry* entry : column_) { if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) @@ -570,7 +591,7 @@ inline void Vector_column::reorder(const Row_index_map& valueMap, if (erasedValues_.find(entry->get_row_index()) == erasedValues_.end()) { if constexpr (Master_matrix::Option_list::has_row_access) { RA_opt::unlink(entry); - if (columnIndex != static_cast(-1)) entry->set_column_index(columnIndex); + if (columnIndex != Master_matrix::template get_null_value()) entry->set_column_index(columnIndex); } entry->set_row_index(valueMap.at(entry->get_row_index())); newColumn.push_back(entry); @@ -623,7 +644,7 @@ inline typename Vector_column::ID_index Vector_column(); if (erasedValues_.empty()) return column_.back()->get_row_index(); auto it = erasedValues_.find(column_.back()->get_row_index()); @@ -634,10 +655,10 @@ inline typename Vector_column::ID_index Vector_columnget_row_index()); } - if (column_.empty()) return -1; + if (column_.empty()) return Master_matrix::template get_null_value(); return column_.back()->get_row_index(); } else { - return Chain_opt::get_pivot(); + return Chain_opt::_get_pivot(); } } @@ -665,9 +686,9 @@ inline typename Vector_column::Field_element Vector_columnget_element(); } else { - if (Chain_opt::get_pivot() == static_cast(-1)) return Field_element(); + if (Chain_opt::_get_pivot() == Master_matrix::template get_null_value()) return Field_element(); for (const Entry* entry : column_) { - if (entry->get_row_index() == Chain_opt::get_pivot()) return entry->get_element(); + if (entry->get_row_index() == Chain_opt::_get_pivot()) return entry->get_element(); } return Field_element(); // should never happen if chain column is used properly } @@ -718,8 +739,7 @@ inline typename Vector_column::reverse_iterator Vector_column -inline typename Vector_column::const_reverse_iterator Vector_column::rend() - const noexcept +inline typename Vector_column::const_reverse_iterator Vector_column::rend() const noexcept { return column_.rend(); } @@ -745,8 +765,8 @@ inline Vector_column& Vector_column::operator+=(Ve if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { // assumes that the addition never zeros out this column. if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { _add(column); @@ -823,16 +843,16 @@ inline Vector_column& Vector_column::multiply_targ if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } else { throw std::invalid_argument("A chain column should not be multiplied by 0."); } } else { if (_multiply_target_and_add(val, column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -882,14 +902,14 @@ inline Vector_column& Vector_column::multiply_sour if constexpr (Master_matrix::Option_list::is_z2) { if (val) { if (_add(column)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { if (_multiply_source_and_add(column, val)) { - Chain_opt::swap_pivots(column); - Dim_opt::swap_dimension(column); + Chain_opt::_swap_pivots(column); + Dim_opt::_swap_dimension(column); } } } else { @@ -910,6 +930,8 @@ inline void Vector_column::push_back(const Entry& entry) { static_assert(Master_matrix::Option_list::is_of_boundary_type, "`push_back` is not available for Chain matrices."); + GUDHI_CHECK(entry.get_row_index() > get_pivot(), "The new row index has to be higher than the current pivot."); + if constexpr (Master_matrix::Option_list::is_z2) { _insert_entry(entry.get_row_index(), column_); } else { @@ -922,6 +944,9 @@ inline Vector_column& Vector_column::operator=(con { static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + // to avoid destroying the column when building from it-self in the for loop below... + if (this == &other) return *this; + Dim_opt::operator=(other); Chain_opt::operator=(other); @@ -955,6 +980,29 @@ inline Vector_column& Vector_column::operator=(con return *this; } +template +inline Vector_column& Vector_column::operator=(Vector_column&& other) noexcept +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignment not enabled with row access option."); + + // to avoid destroying the column before building from it-self... + if (&column_ == &(other.column_)) return *this; + + Dim_opt::operator=(std::move(other)); + Chain_opt::operator=(std::move(other)); + + for (auto* entry : column_) { + if (entry != nullptr) _delete_entry(entry); + } + + column_ = std::move(other.column_); + erasedValues_ = std::move(other.erasedValues_); + operators_ = std::exchange(other.operators_, nullptr); + entryPool_ = std::exchange(other.entryPool_, nullptr); + + return *this; +} + template inline std::size_t Vector_column::compute_hash_value() { @@ -983,11 +1031,11 @@ inline void Vector_column::_delete_entry(typename Column_support: } template -inline typename Vector_column::Entry* Vector_column::_insert_entry( - const Field_element& value, ID_index rowIndex, Column_support& column) +inline typename Vector_column::Entry* +Vector_column::_insert_entry(const Field_element& value, ID_index rowIndex, Column_support& column) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column.push_back(newEntry); RA_opt::insert_entry(rowIndex, newEntry); @@ -1004,7 +1052,7 @@ template inline void Vector_column::_insert_entry(ID_index rowIndex, Column_support& column) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column.push_back(newEntry); RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -1017,7 +1065,7 @@ template inline void Vector_column::_update_entry(const Field_element& value, ID_index rowIndex, Index position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); newEntry->set_element(value); column_[position] = newEntry; RA_opt::insert_entry(rowIndex, newEntry); @@ -1031,7 +1079,7 @@ template inline void Vector_column::_update_entry(ID_index rowIndex, Index position) { if constexpr (Master_matrix::Option_list::has_row_access) { - Entry* newEntry = entryPool_->construct(RA_opt::columnIndex_, rowIndex); + Entry* newEntry = entryPool_->construct(RA_opt::get_column_index(), rowIndex); column_[position] = newEntry; RA_opt::insert_entry(rowIndex, newEntry); } else { @@ -1075,8 +1123,7 @@ inline bool Vector_column::_add(const Entry_range& column) if constexpr (!Master_matrix::Option_list::is_z2) operators_->add_inplace(targetElement, itSource->get_element()); }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); } - ); + [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }); column_.swap(newColumn); @@ -1087,7 +1134,7 @@ template template inline bool Vector_column::_multiply_target_and_add(const Field_element& val, const Entry_range& column) { - if (val == 0u) { + if (val == 0U) { if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { throw std::invalid_argument("A chain column should not be multiplied by 0."); // this would not only mess up the base, but also the pivots stored. @@ -1125,8 +1172,7 @@ inline bool Vector_column::_multiply_target_and_add(const Field_e [&](Field_element& targetElement, typename Entry_range::const_iterator& itSource) { operators_->multiply_and_add_inplace_front(targetElement, val, itSource->get_element()); }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); } - ); + [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }); column_.swap(newColumn); @@ -1137,7 +1183,7 @@ template template inline bool Vector_column::_multiply_source_and_add(const Entry_range& column, const Field_element& val) { - if (val == 0u || column.begin() == column.end()) { + if (val == 0U || column.begin() == column.end()) { return false; } @@ -1155,8 +1201,7 @@ inline bool Vector_column::_multiply_source_and_add(const Entry_r [&](Field_element& targetElement, typename Entry_range::const_iterator& itSource) { operators_->multiply_and_add_inplace_back(itSource->get_element(), val, targetElement); }, - [&](Entry* entryTarget) { newColumn.push_back(entryTarget); } - ); + [&](Entry* entryTarget) { newColumn.push_back(entryTarget); }); column_.swap(newColumn); @@ -1197,8 +1242,13 @@ inline bool Vector_column::_generic_add(const Entry_range& column updateSourceIterator(itSource); if (itTarget == column_.end() || itSource == column.end()) break; - _generic_merge_entry_to_column(*this, itSource, itTarget, - process_target, process_source, update_target1, update_target2, + _generic_merge_entry_to_column(*this, + itSource, + itTarget, + std::forward(process_target), + std::forward(process_source), + std::forward(update_target1), + std::forward(update_target2), pivotIsZeroed); } @@ -1206,7 +1256,7 @@ inline bool Vector_column::_generic_add(const Entry_range& column updateSourceIterator(itSource); if (itSource == column.end()) break; - process_source(itSource, column_.end()); + std::forward(process_source)(itSource, column_.end()); ++itSource; } @@ -1214,7 +1264,7 @@ inline bool Vector_column::_generic_add(const Entry_range& column updateTargetIterator(itTarget); if (itTarget == column_.end()) break; - process_target(*itTarget); + std::forward(process_target)(*itTarget); ++itTarget; } @@ -1236,7 +1286,8 @@ inline bool Vector_column::_generic_add(const Entry_range& column */ template struct std::hash > { - std::size_t operator()(const Gudhi::persistence_matrix::Vector_column& column) const { + std::size_t operator()(const Gudhi::persistence_matrix::Vector_column& column) const + { return column.compute_hash_value(); } }; diff --git a/multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h b/multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h new file mode 100644 index 00000000..e3426fea --- /dev/null +++ b/multipers/gudhi/gudhi/Persistence_matrix/index_mapper.h @@ -0,0 +1,58 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file index_mapper.h + * @author Hannah Schreiber + * @brief Contains the Gudhi::persistence_matrix::Index_mapper class and + * Gudhi::persistence_matrix::Dummy_index_mapper structure. + */ + +#ifndef PM_INDEX_MAPPER_H +#define PM_INDEX_MAPPER_H + +namespace Gudhi { +namespace persistence_matrix { + +// Note: this would be a good candidate for std::optional instead of a mixin as its only role for now +// is to store a map. If this does not change later. + +/** + * @private + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inherited instead of @ref Index_mapper. + */ +struct Dummy_index_mapper { + friend void swap([[maybe_unused]] Dummy_index_mapper& d1, [[maybe_unused]] Dummy_index_mapper& d2) noexcept {} +}; + +/** + * @private + * @ingroup persistence_matrix + * + * @brief Map container. Though for translation between different index types. + * + * @tparam Map Map type + */ +template +struct Index_mapper { + using Index_map = Map; + + Index_map map_; + + friend void swap(Index_mapper& mapper1, Index_mapper& mapper2) noexcept { mapper1.map_.swap(mapper2.map_); } +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_INDEX_MAPPER_H diff --git a/multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h b/multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h index 30ce121a..6cb98d18 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/matrix_dimension_holders.h @@ -32,51 +32,56 @@ namespace persistence_matrix { * Inherited instead of @ref Matrix_max_dimension_holder or @ref Matrix_all_dimension_holder, when the maximal * dimension of a matrix is not stored. */ -struct Dummy_matrix_dimension_holder -{ +struct Dummy_matrix_dimension_holder { template - Dummy_matrix_dimension_holder([[maybe_unused]] Dimension maximalDimension) {} + Dummy_matrix_dimension_holder([[maybe_unused]] Dimension maximalDimension) + {} friend void swap([[maybe_unused]] Dummy_matrix_dimension_holder& d1, - [[maybe_unused]] Dummy_matrix_dimension_holder& d2) {} + [[maybe_unused]] Dummy_matrix_dimension_holder& d2) noexcept + {} }; +// TODO: find an easy way to give access to get_null_value() to replace the -1s + /** * @ingroup persistence_matrix * * @brief Class managing the maximal dimension of a cell represented in the inheriting matrix, when the option of * cell removal is not enabled. - * + * * @tparam Dimension Dimension value type. Has to be an integer type. * If unsigned, the maximal value of the type should not be attained during a run. */ template -class Matrix_max_dimension_holder +class Matrix_max_dimension_holder { public: /** * @brief Default constructor. If a dimension is specified, stores it as the maximal value. - * + * * @param maximalDimension Value of the maximal dimension. Has to be either positive or -1. Default value: -1. */ - Matrix_max_dimension_holder(Dimension maximalDimension = -1) : maxDim_(maximalDimension){}; + Matrix_max_dimension_holder(Dimension maximalDimension = -1) : maxDim_(maximalDimension) {}; /** * @brief Copy constructor. - * + * * @param toCopy Matrix to copy. */ - Matrix_max_dimension_holder(const Matrix_max_dimension_holder& toCopy) : maxDim_(toCopy.maxDim_){}; + Matrix_max_dimension_holder(const Matrix_max_dimension_holder& toCopy) = default; /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Matrix_max_dimension_holder(Matrix_max_dimension_holder&& other) noexcept - : maxDim_(std::exchange(other.maxDim_, -1)){}; + : maxDim_(std::exchange(other.maxDim_, -1)) {}; + + ~Matrix_max_dimension_holder() = default; /** * @brief Returns the maximal dimension of a cell represented in the matrix. - * + * * @return The maximal dimension. */ Dimension get_max_dimension() const { return maxDim_; }; @@ -84,23 +89,35 @@ class Matrix_max_dimension_holder /** * @brief Assign operator. */ - Matrix_max_dimension_holder& operator=(const Matrix_max_dimension_holder& other) { - std::swap(maxDim_, other.maxDim_); + Matrix_max_dimension_holder& operator=(const Matrix_max_dimension_holder& other) = default; + + /** + * @brief Move assign operator. + */ + Matrix_max_dimension_holder& operator=(Matrix_max_dimension_holder&& other) noexcept + { + maxDim_ = std::exchange(other.maxDim_, -1); return *this; }; + /** * @brief Swap operator. */ - friend void swap(Matrix_max_dimension_holder& matrix1, Matrix_max_dimension_holder& matrix2) { + friend void swap(Matrix_max_dimension_holder& matrix1, Matrix_max_dimension_holder& matrix2) noexcept + { std::swap(matrix1.maxDim_, matrix2.maxDim_); } protected: - Dimension maxDim_; /**< Current maximal dimension. */ - - void update_up(Dimension dimension) { + void _update_up(Dimension dimension) + { if (maxDim_ == -1 || maxDim_ < dimension) maxDim_ = dimension; }; + + void _reset() { maxDim_ = -1; } + + private: + Dimension maxDim_; /**< Current maximal dimension. */ }; /** @@ -108,41 +125,44 @@ class Matrix_max_dimension_holder * * @brief Class managing the maximal dimension of a cell represented in the inheriting matrix, when the option of * cell removal is enabled. - * + * * @tparam Dimension Dimension value type. Has to be an integer type. * If unsigned, the maximal value of the type should not be attained during a run. */ template -class Matrix_all_dimension_holder +class Matrix_all_dimension_holder { public: /** * @brief Default constructor. If a dimension is specified, stores it as the maximal value. - * + * * @param maximalDimension Value of the maximal dimension. Has to be either positive or -1. Default value: -1. */ Matrix_all_dimension_holder(Dimension maximalDimension = -1) - : dimensions_(maximalDimension < 0 ? 0 : maximalDimension + 1, 0), maxDim_(maximalDimension) { + : dimensions_(maximalDimension < 0 ? 0 : maximalDimension + 1, 0), maxDim_(maximalDimension) + { if (maxDim_ != -1) dimensions_[maxDim_] = 1; }; + /** * @brief Copy constructor. - * + * * @param toCopy Matrix to copy. */ - Matrix_all_dimension_holder(const Matrix_all_dimension_holder& toCopy) - : dimensions_(toCopy.dimensions_), maxDim_(toCopy.maxDim_){}; + Matrix_all_dimension_holder(const Matrix_all_dimension_holder& toCopy) = default; /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Matrix_all_dimension_holder(Matrix_all_dimension_holder&& other) noexcept - : dimensions_(std::move(other.dimensions_)), maxDim_(std::exchange(other.maxDim_, -1)){}; + : dimensions_(std::move(other.dimensions_)), maxDim_(std::exchange(other.maxDim_, -1)) {}; + + ~Matrix_all_dimension_holder() = default; /** * @brief Returns the maximal dimension of a cell represented in the matrix. - * + * * @return The maximal dimension. */ Dimension get_max_dimension() const { return maxDim_; }; @@ -150,34 +170,51 @@ class Matrix_all_dimension_holder /** * @brief Assign operator. */ - Matrix_all_dimension_holder& operator=(Matrix_all_dimension_holder other) { - std::swap(maxDim_, other.maxDim_); - dimensions_.swap(other.dimensions_); + Matrix_all_dimension_holder& operator=(const Matrix_all_dimension_holder& other) = default; + + /** + * @brief Move assign operator. + */ + Matrix_all_dimension_holder& operator=(Matrix_all_dimension_holder&& other) noexcept + { + dimensions_ = std::move(other.dimensions_); + maxDim_ = std::exchange(other.maxDim_, -1); return *this; }; + /** * @brief Swap operator. */ - friend void swap(Matrix_all_dimension_holder& matrix1, Matrix_all_dimension_holder& matrix2) { + friend void swap(Matrix_all_dimension_holder& matrix1, Matrix_all_dimension_holder& matrix2) noexcept + { std::swap(matrix1.maxDim_, matrix2.maxDim_); matrix1.dimensions_.swap(matrix2.dimensions_); } protected: - std::vector dimensions_; /**< Number of cells by dimension. */ - Dimension maxDim_; /**< Current maximal dimension. */ - - void update_up(unsigned int dimension) { + void _update_up(unsigned int dimension) + { if (dimensions_.size() <= dimension) dimensions_.resize(dimension + 1, 0); ++(dimensions_[dimension]); maxDim_ = dimensions_.size() - 1; }; - void update_down(unsigned int dimension) { + void _update_down(unsigned int dimension) + { --(dimensions_[dimension]); // assumes dimension already exists and is not 0 while (!dimensions_.empty() && dimensions_.back() == 0) dimensions_.pop_back(); maxDim_ = dimensions_.size() - 1; }; + + void _reset() + { + dimensions_.clear(); + maxDim_ = -1; + } + + private: + std::vector dimensions_; /**< Number of cells by dimension. */ + Dimension maxDim_; /**< Current maximal dimension. */ }; } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h b/multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h index 925f573f..3f6fce21 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/matrix_row_access.h @@ -29,11 +29,11 @@ namespace persistence_matrix { * @brief Empty structure. * Inherited instead of @ref Matrix_row_access, when the the row access is not enabled. */ -struct Dummy_matrix_row_access -{ - Dummy_matrix_row_access([[maybe_unused]] unsigned int numberOfRows = 0){}; +struct Dummy_matrix_row_access { + Dummy_matrix_row_access([[maybe_unused]] unsigned int numberOfRows = 0) {}; - friend void swap([[maybe_unused]] Dummy_matrix_row_access& d1, [[maybe_unused]] Dummy_matrix_row_access& d2) {} + friend void swap([[maybe_unused]] Dummy_matrix_row_access& d1, [[maybe_unused]] Dummy_matrix_row_access& d2) noexcept + {} }; /** @@ -41,7 +41,7 @@ struct Dummy_matrix_row_access * @ingroup persistence_matrix * * @brief Class managing the row access for the inheriting matrix. - * + * * @tparam Row Either boost::intrusive::list if @ref PersistenceMatrixOptions::has_intrusive_rows * is true, or std::set otherwise. * @tparam Row_container Either std::map if @ref PersistenceMatrixOptions::has_removable_rows is @@ -50,28 +50,31 @@ struct Dummy_matrix_row_access * @tparam ID_index @ref IDIdx index type. */ template -class Matrix_row_access +class Matrix_row_access { public: /** * @brief Default constructor. */ - Matrix_row_access() : rows_(new Row_container()){}; + Matrix_row_access() : rows_(new Row_container()) {}; + /** * @brief Constructor reserving space for the given number of rows. * * Has only an effect if @ref PersistenceMatrixOptions::has_removable_rows is false. - * + * * @param numberOfRows Number of rows to reserve space for. */ - Matrix_row_access(unsigned int numberOfRows) : rows_(new Row_container()) { + Matrix_row_access(unsigned int numberOfRows) : rows_(new Row_container()) + { if constexpr (!has_removable_rows) { rows_->resize(numberOfRows); } } + /** * @brief Copy constructor. - * + * * @param toCopy Matrix to copy. */ Matrix_row_access(const Matrix_row_access& toCopy) @@ -81,12 +84,14 @@ class Matrix_row_access rows_->resize(toCopy.rows_->size()); } } + /** * @brief Move constructor. - * + * * @param other Matrix to move. */ Matrix_row_access(Matrix_row_access&& other) noexcept : rows_(std::exchange(other.rows_, nullptr)) {} + /** * @brief Destructor. */ @@ -94,41 +99,46 @@ class Matrix_row_access /** * @brief Returns the row at the given @ref rowindex "row index". - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. - * + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occurred. * @return Reference to the row. */ - Row& get_row(ID_index rowIndex) { + Row& get_row(ID_index rowIndex) + { if constexpr (has_removable_rows) { return rows_->at(rowIndex); } else { return rows_->operator[](rowIndex); } } + /** * @brief Returns the row at the given @ref rowindex "row index". - * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. - * + * The type of the row depends on the chosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occurred. * @return Const reference to the row. */ - const Row& get_row(ID_index rowIndex) const { + const Row& get_row(ID_index rowIndex) const + { if constexpr (has_removable_rows) { return rows_->at(rowIndex); } else { return rows_->operator[](rowIndex); } } + /** * @brief Only available if @ref PersistenceMatrixOptions::has_removable_rows is true. Removes the given row * from the row container if the row exists and is empty. - * + * * @param rowIndex @ref rowindex "Row index" of the row to remove. */ - void erase_empty_row(ID_index rowIndex) { + void erase_empty_row(ID_index rowIndex) + { static_assert(has_removable_rows, "'erase_empty_row' is not implemented for the chosen options."); auto it = rows_->find(rowIndex); @@ -138,19 +148,43 @@ class Matrix_row_access /** * @brief Assign operator. */ - Matrix_row_access& operator=(const Matrix_row_access& other) { + Matrix_row_access& operator=(const Matrix_row_access& other) + { if constexpr (has_removable_rows) rows_->reserve(other.rows_->size()); else rows_->resize(other.rows_->size()); return *this; } + + /** + * @brief Move assign operator. + */ + Matrix_row_access& operator=(Matrix_row_access&& other) noexcept + { + rows_ = std::exchange(other.rows_, nullptr); + return *this; + } + /** * @brief Swap operator. */ - friend void swap(Matrix_row_access& matrix1, Matrix_row_access& matrix2) { std::swap(matrix1.rows_, matrix2.rows_); } + friend void swap(Matrix_row_access& matrix1, Matrix_row_access& matrix2) noexcept + { + std::swap(matrix1.rows_, matrix2.rows_); + } protected: + Row_container* _get_rows_ptr() const { return rows_; } + + void _resize(ID_index size) + { + static_assert(!has_removable_rows, "'_resize' is not implemented for the chosen options."); + + if (rows_->size() <= size) rows_->resize(size + 1); + } + + private: /** * @brief Row container. A pointer to facilitate column swaps when two matrices are swapped. * Has to be destroyed after matrix_, therefore has to be inherited. diff --git a/multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h b/multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h index e6afe9fc..f1f3c1e7 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/ru_pairing.h @@ -20,11 +20,12 @@ #include -#include "boundary_cell_position_to_id_mapper.h" - namespace Gudhi { namespace persistence_matrix { +template +class RU_barcode_swap; + /** * @ingroup persistence_matrix * @@ -32,9 +33,8 @@ namespace persistence_matrix { * Inherited instead of @ref RU_pairing, when the computation of the barcode was not enabled or if the pairing * is already managed by the vine update classes. */ -struct Dummy_ru_pairing -{ - friend void swap([[maybe_unused]] Dummy_ru_pairing& d1, [[maybe_unused]] Dummy_ru_pairing& d2) {} +struct Dummy_ru_pairing { + friend void swap([[maybe_unused]] Dummy_ru_pairing& d1, [[maybe_unused]] Dummy_ru_pairing& d2) noexcept {} }; /** @@ -42,36 +42,23 @@ struct Dummy_ru_pairing * @ingroup persistence_matrix * * @brief Class managing the barcode for @ref RU_matrix if the option was enabled. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class RU_pairing : public std::conditional< - Master_matrix::Option_list::has_removable_columns, - Cell_position_to_ID_mapper, - Dummy_pos_mapper - >::type +class RU_pairing { - protected: - using Pos_index = typename Master_matrix::Pos_index; - using ID_index = typename Master_matrix::ID_index; - //PIDM = Position to ID Map - using PIDM = typename std::conditional, - Dummy_pos_mapper - >::type; - public: - using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ + using Barcode = typename Master_matrix::Barcode; /**< Barcode type. */ /** * @brief Default constructor. */ - RU_pairing() : PIDM() {} + RU_pairing() = default; /** * @brief Returns the current barcode which is maintained at any insertion, removal or vine swap. - * + * * @return Const reference to the barcode. */ const Barcode& get_current_barcode() const { return barcode_; } @@ -79,25 +66,22 @@ class RU_pairing : public std::conditional< /** * @brief Swap operator. */ - friend void swap(RU_pairing& pairing1, RU_pairing& pairing2) { - swap(static_cast(pairing1), static_cast(pairing2)); + friend void swap(RU_pairing& pairing1, RU_pairing& pairing2) noexcept + { pairing1.barcode_.swap(pairing2.barcode_); pairing1.indexToBar_.swap(pairing2.indexToBar_); pairing1.idToPosition_.swap(pairing2.idToPosition_); } protected: + using Pos_index = typename Master_matrix::Pos_index; + using ID_index = typename Master_matrix::ID_index; using Dimension = typename Master_matrix::Dimension; - using Dictionary = typename Master_matrix::Bar_dictionary; - Barcode barcode_; /**< Bar container. */ - Dictionary indexToBar_; /**< Map from @ref MatIdx index to bar index. */ - /** - * @brief Map from cell ID to cell position. Only stores a pair if ID != position. - */ - std::unordered_map idToPosition_; //TODO: test other map types + void _reserve(unsigned int numberOfColumns) { indexToBar_.reserve(numberOfColumns); } - void _update_barcode(ID_index birthPivot, Pos_index death) { + void _update_barcode(ID_index birthPivot, Pos_index death) + { auto it = idToPosition_.find(birthPivot); Pos_index pivotBirth = it == idToPosition_.end() ? birthPivot : it->second; if constexpr (Master_matrix::hasFixedBarcode || !Master_matrix::Option_list::has_removable_columns) { @@ -110,8 +94,9 @@ class RU_pairing : public std::conditional< } } - void _add_bar(Dimension dim, Pos_index birth) { - barcode_.emplace_back(birth, -1, dim); + void _add_bar(Dimension dim, Pos_index birth) + { + barcode_.emplace_back(birth, Master_matrix::template get_null_value(), dim); if constexpr (Master_matrix::hasFixedBarcode || !Master_matrix::Option_list::has_removable_columns) { indexToBar_.push_back(barcode_.size() - 1); } else { @@ -119,35 +104,60 @@ class RU_pairing : public std::conditional< } } - void _remove_last(Pos_index eventIndex) { + void _remove_last(Pos_index eventIndex) + { static_assert(Master_matrix::Option_list::has_removable_columns, "_remove_last not available."); + constexpr const Pos_index nullDeath = Master_matrix::template get_null_value(); if constexpr (Master_matrix::hasFixedBarcode) { auto& bar = barcode_[indexToBar_[eventIndex]]; - if (bar.death == static_cast(-1)) { // birth - barcode_.pop_back(); // sorted by birth and eventIndex has to be the highest one - } else { // death - bar.death = -1; + if (bar.death == nullDeath) { // birth + barcode_.pop_back(); // sorted by birth and eventIndex has to be the highest one + } else { // death + bar.death = nullDeath; }; indexToBar_.pop_back(); } else { // birth order eventually shuffled by vine updates. No sort possible to keep the matchings. auto it = indexToBar_.find(eventIndex); typename Barcode::iterator bar = it->second; - if (bar->death == static_cast(-1)) + if (bar->death == nullDeath) barcode_.erase(bar); else - bar->death = -1; + bar->death = nullDeath; indexToBar_.erase(it); } - auto it = PIDM::map_.find(eventIndex); - if (it != PIDM::map_.end()){ + auto& map = static_cast(this)->positionToID_; + auto it = map.find(eventIndex); + if (it != map.end()) { idToPosition_.erase(it->second); - PIDM::map_.erase(it); + map.erase(it); } } + + void _insert_id_position(ID_index id, Pos_index pos) { idToPosition_.emplace(id, pos); } + + void _reset() + { + barcode_.clear(); + indexToBar_.clear(); + } + + private: + using Dictionary = typename Master_matrix::Bar_dictionary; + + // could also just mark everything as protected as RU_barcode_swap inherits from RU_pairing + // but this way, it marks a better difference between "class using this mixin" with "class extending this mixin" + friend RU_barcode_swap; + + Barcode barcode_; /**< Bar container. */ + Dictionary indexToBar_; /**< Map from @ref MatIdx index to bar index. */ + /** + * @brief Map from cell ID to cell position. Only stores a pair if ID != position. + */ + std::unordered_map idToPosition_; // TODO: test other map types }; } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h b/multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h index 453d5240..b5e1aa81 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/ru_rep_cycles.h @@ -18,16 +18,9 @@ #ifndef PM_RU_REP_CYCLES_H #define PM_RU_REP_CYCLES_H -#include #include //std::move #include //std::sort #include -// #include -#if BOOST_VERSION >= 108100 -#include -#else -#include -#endif #include @@ -41,10 +34,10 @@ namespace persistence_matrix { * Inherited instead of @ref RU_representative_cycles, when the computation of the representative cycles * were not enabled. */ -struct Dummy_ru_representative_cycles -{ +struct Dummy_ru_representative_cycles { friend void swap([[maybe_unused]] Dummy_ru_representative_cycles& d1, - [[maybe_unused]] Dummy_ru_representative_cycles& d2) {} + [[maybe_unused]] Dummy_ru_representative_cycles& d2) noexcept + {} }; // TODO: add coefficients ? Only Z2 token into account for now. @@ -53,323 +46,282 @@ struct Dummy_ru_representative_cycles * @ingroup persistence_matrix * * @brief Class managing the representative cycles for @ref RU_matrix if the option was enabled. - * + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ template -class RU_representative_cycles +class RU_representative_cycles { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Bar = typename Master_matrix::Bar; /**< Bar type. */ - using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ - using Cycle_border = std::vector; - using Cycle_borders = std::vector; - struct hashCycle { - size_t operator()(const Cycle_border& cycle) const { - std::hash hasher; - size_t answer = 0; - for (ID_index i : cycle) { - answer ^= hasher(i) + 0x9e3779b9 + (answer << 6) + (answer >> 2); - } - return answer; - } - }; -#if BOOST_VERSION >= 108100 - using Cycle_borders_tmp = boost::unordered_flat_set; -#else - using Cycle_borders_tmp = std::unordered_set; -#endif -#if BOOST_VERSION >= 108100 - using Cycle_unreduced_borders_tmp = boost::unordered_flat_set; -#else - using Cycle_unreduced_borders_tmp = std::unordered_set; -#endif + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using Cycle = typename Master_matrix::Cycle; /**< Cycle type. */ /** * @brief Default constructor. */ - RU_representative_cycles(); - /** - * @brief Copy constructor. - * - * @param matrixToCopy Matrix to copy. - */ - RU_representative_cycles(const RU_representative_cycles& matrixToCopy); - /** - * @brief Move constructor. - * - * @param other Matrix to move. - */ - RU_representative_cycles(RU_representative_cycles&& other) noexcept; + RU_representative_cycles() = default; /** * @brief Computes the current representative cycles of the matrix. */ void update_representative_cycles(); - // /** - // * @brief Returns the current representative cycles. If the matrix is modified later after the first call, - // * @ref update_representative_cycles has to be called to update the returned cycles. - // * - // * @return A const reference to a vector of @ref Matrix::Cycle containing all representative cycles. - // */ - // const std::vector& get_representative_cycles(); - // /** - // * @brief Returns the representative cycle corresponding to the given bar. - // * If the matrix is modified later after the first call, - // * @ref update_representative_cycles has to be called to update the returned cycles. - // * - // * @param bar Bar corresponding to the wanted representative cycle. - // * @return A const reference to the representative cycle. - // */ - // const Cycle& get_representative_cycle(const Bar& bar); - /** * @brief Returns the current representative cycles. If the matrix is modified later after the first call, * @ref update_representative_cycles has to be called to update the returned cycles. - * + * * @return A const reference to a vector of @ref Matrix::Cycle containing all representative cycles. */ - std::vector get_representative_cycles_as_borders(bool detailed = false); - + const std::vector& get_representative_cycles(); /** - * @brief Assign operator. + * @brief Returns the representative cycle corresponding to the given bar. + * If the matrix is modified later after the first call, + * @ref update_representative_cycles has to be called to update the returned cycles. + * + * @param bar Bar corresponding to the wanted representative cycle. + * @return A const reference to the representative cycle. */ - RU_representative_cycles& operator=(RU_representative_cycles other); + const Cycle& get_representative_cycle(const Bar& bar); + /** * @brief Swap operator. */ - friend void swap(RU_representative_cycles& base1, RU_representative_cycles& base2) { + friend void swap(RU_representative_cycles& base1, RU_representative_cycles& base2) noexcept + { base1.representativeCycles_.swap(base2.representativeCycles_); - base1.u_transposed_.swap(base2.u_transposed_); - // base1.birthToCycle_.swap(base2.birthToCycle_); + base1.birthToCycle_.swap(base2.birthToCycle_); } + protected: + void _reset(); + private: using Master_RU_matrix = typename Master_matrix::Master_RU_matrix; + using Inverse_column = Cycle; - std::vector representativeCycles_; /**< Cycle container. */ - // std::vector birthToCycle_; /**< Map from birth index to cycle index. */ - std::vector u_transposed_; - - void _get_initial_borders(Index idx, Cycle_borders_tmp& borders); - Cycle_border _get_border(Index uIndex); - Cycle_border _get_dependent_border(Index uIndex); + std::vector representativeCycles_; /**< Cycle container. */ + std::vector birthToCycle_; /**< Map from birth index to cycle index. */ constexpr Master_RU_matrix* _matrix() { return static_cast(this); } - constexpr const Master_RU_matrix* _matrix() const { return static_cast(this); } -}; -template -inline RU_representative_cycles::RU_representative_cycles() -{} + constexpr const Master_RU_matrix* _matrix() const { return static_cast(this); } -template -inline RU_representative_cycles::RU_representative_cycles( - const RU_representative_cycles& matrixToCopy) - : representativeCycles_(matrixToCopy.representativeCycles_), - // birthToCycle_(matrixToCopy.birthToCycle_), - u_transposed_(matrixToCopy.u_transposed_) -{} + void _retrieve_cycle_from_r(Index colIdx, Index repIdx); + void _retrieve_cycle_from_u(Index colIdx, Index repIdx); + Inverse_column _get_inverse(Index c); +}; template -inline RU_representative_cycles::RU_representative_cycles( - RU_representative_cycles&& other) noexcept - : representativeCycles_(std::move(other.representativeCycles_)), - // birthToCycle_(std::move(other.birthToCycle_)), - u_transposed_(std::move(other.u_transposed_)) -{} - -//TODO: u_transposed_ as a vector of Index and another vector giving position + length of each cycle? -template -inline void RU_representative_cycles::update_representative_cycles() +inline void RU_representative_cycles::update_representative_cycles() { - //WARNING: this was only thought for the multipers interface, it is not definitive and does not - // cover all cases. - static_assert(Master_matrix::Option_list::has_column_pairings && Master_matrix::Option_list::is_z2, - "Needs an ID to Pos map."); - - auto rsize = _matrix()->reducedMatrixR_.get_number_of_columns(); + auto nberColumns = _matrix()->reducedMatrixR_.get_number_of_columns(); + Index nullValue = Master_matrix::template get_null_value(); representativeCycles_.clear(); - representativeCycles_.reserve(rsize); - - auto usize = _matrix()->mirrorMatrixU_.get_number_of_columns(); - u_transposed_.clear(); - u_transposed_.resize(usize); + birthToCycle_.clear(); + birthToCycle_.resize(nberColumns, nullValue); - for (Index i = 0; i < usize; i++) { + Index c = 0; + for (Index i = 0; i < nberColumns; i++) { if (_matrix()->reducedMatrixR_.is_zero_column(i)) { - representativeCycles_.push_back(i); + birthToCycle_[i] = c; + ++c; } - if constexpr (Master_matrix::Option_list::column_type == Column_types::HEAP || - Master_matrix::Option_list::column_type == Column_types::VECTOR) { - // TODO: have a better way to do this. For now, one cannot use the column iterators for that. - unsigned int j = 0; - for (const auto& entry : _matrix()->mirrorMatrixU_.get_column(i).get_content()) { - if (entry != 0){ - u_transposed_[j].push_back(i); - } - ++j; - } - } else { - for (const auto& entry : _matrix()->mirrorMatrixU_.get_column(i)) { - u_transposed_[entry.get_row_index()].push_back(i); + } + + representativeCycles_.resize(c); + for (Index i = 0; i < nberColumns; ++i) { + if (birthToCycle_[i] != nullValue) { + Index colIdx = _matrix()->_get_column_with_pivot(i); + if (colIdx == nullValue) { + _retrieve_cycle_from_u(i, birthToCycle_[i]); + } else { + _retrieve_cycle_from_r(colIdx, birthToCycle_[i]); } } } } -// template -// inline const std::vector::Cycle>& -// RU_representative_cycles::get_representative_cycles() -// { -// if (representativeCycles_.empty()) update_representative_cycles(); -// return representativeCycles_; -// } - -// template -// inline const typename RU_representative_cycles::Cycle& -// RU_representative_cycles::get_representative_cycle(const Bar& bar) -// { -// if (representativeCycles_.empty()) update_representative_cycles(); -// return representativeCycles_[birthToCycle_[bar.birth]]; -// } - -//TODO: not store cycle borders in vectors of vectors of vectors template -inline std::vector::Cycle_borders> -RU_representative_cycles::get_representative_cycles_as_borders(bool detailed) +inline const std::vector::Cycle>& +RU_representative_cycles::get_representative_cycles() { - static_assert(Master_matrix::Option_list::is_z2, "Only available for Z2 coefficients for now."); + if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_; +} +template +inline const typename RU_representative_cycles::Cycle& +RU_representative_cycles::get_representative_cycle(const Bar& bar) +{ if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_[birthToCycle_[bar.birth]]; +} - std::vector cycles(representativeCycles_.size()); - unsigned int i = 0; - for (const auto& cycleIndex : representativeCycles_){ - const auto& cycle = u_transposed_[cycleIndex]; - assert(_matrix()->reducedMatrixR_.is_zero_column(cycle.back())); - if (cycle.size() != 1){ //cycle.size == 1 -> border is empty - if (detailed) { - Cycle_borders_tmp cbt; - for (unsigned int j = 0; j < cycle.size() - 1; ++j){ - assert(!_matrix()->reducedMatrixR_.is_zero_column(cycle[j])); - _get_initial_borders(cycle[j], cbt); - } - cycles[i] = Cycle_borders(cbt.begin(), cbt.end()); - cycles[i].push_back(_get_dependent_border(cycle.back())); +template +inline void RU_representative_cycles::_retrieve_cycle_from_r(Index colIdx, Index repIdx) +{ + if constexpr (is_well_behaved::value) { + const auto& col = _matrix()->reducedMatrixR_.get_column(colIdx); + representativeCycles_[repIdx].resize(col.size()); + Index j = 0; + for (const auto& cell : col) { + if constexpr (Master_matrix::Option_list::is_z2) { + representativeCycles_[repIdx][j] = cell.get_row_index(); } else { - cycles[i].resize(cycle.size()); - for (unsigned int j = 0; j < cycle.size() - 1; ++j){ - cycles[i][j] = _get_border(cycle[j]); + representativeCycles_[repIdx][j].first = cell.get_row_index(); + representativeCycles_[repIdx][j].second = cell.get_element(); + } + ++j; + } + } else { + auto col = _matrix()->reducedMatrixR_.get_column(colIdx).get_content(); + for (Index j = 0; j < col.size(); ++j) { + if (col[j] != 0) { + if constexpr (Master_matrix::Option_list::is_z2) { + representativeCycles_[repIdx].push_back(j); + } else { + representativeCycles_[repIdx].push_back({j, col[j]}); } - cycles[i].back() = _get_dependent_border(cycle.back()); } - } else { - cycles[i].push_back({}); } - - ++i; } - return cycles; } template -inline RU_representative_cycles& RU_representative_cycles::operator=( - RU_representative_cycles other) +inline void RU_representative_cycles::_retrieve_cycle_from_u(Index colIdx, Index repIdx) { - representativeCycles_.swap(other.representativeCycles_); - u_transposed_.swap(other.u_transposed_); - // birthToCycle_.swap(other.birthToCycle_); - return *this; + // TODO: if rep_cycles true but not vineyards, this could be avoided by directly computing V instead of U + representativeCycles_[repIdx] = _get_inverse(colIdx); } -//TODO: try avoid storing the vectors in the unordered set, when it is not reduced as it adds run time for hash -//computation and possibly comparaison of vectors. Treat them separately. See if it goes faster, or if it was marginal template -inline void RU_representative_cycles::_get_initial_borders(Index idx, - Cycle_borders_tmp& borders) { - const Cycle& cycle = u_transposed_[idx]; - assert(cycle.back() == idx); - - auto add_to = [](const std::vector& b, Cycle_borders_tmp& borders){ - auto p = borders.insert(b); - if (!p.second) borders.erase(p.first); +inline typename RU_representative_cycles::Inverse_column +RU_representative_cycles::_get_inverse(Index c) +{ + using E = typename Master_matrix::Element; + auto& matrix = _matrix()->mirrorMatrixU_; + auto size = matrix.get_number_of_columns(); + [[maybe_unused]] const auto& op = _matrix()->operators_; + Inverse_column res; + + auto _last_diagonal_value = [&]() -> E { + auto& col = matrix.get_column(size - 1); + if (col.is_empty()) return 0; // happens only if the user multiplied by 0 a column in R + // if column not empty, pivot has to be on the diagonal + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + return op->get_inverse(matrix.get_column(size - 1).get_pivot_value()); + } }; - for (unsigned int j = 0; j < cycle.size() - 1; ++j){ - _get_initial_borders(cycle[j], borders); - } + auto _push_cell = [&](auto i, E e) -> void { + if constexpr (Master_matrix::Option_list::is_z2) { + if (e) res.push_back(i); + } else { + if (e != op->get_additive_identity()) res.push_back({i, e}); + } + }; - add_to(_get_dependent_border(idx), borders); -} + auto _substract = [&](E& e, auto resIt, const auto& cell) -> void { + if constexpr (Master_matrix::Option_list::is_z2) { + if (resIt != res.rend() && *resIt == cell.get_row_index()) e = !e; + } else { + if (resIt != res.rend() && resIt->first == cell.get_row_index()) + op->subtract_inplace_front(e, cell.get_element() * resIt->second); + } + }; -template -inline typename RU_representative_cycles::Cycle_border -RU_representative_cycles::_get_border(Index uIndex) { - const auto& col = _matrix()->reducedMatrixR_.get_column(uIndex); - Cycle_border res; - - unsigned int j = 0; - if constexpr (Master_matrix::Option_list::column_type == Column_types::HEAP || - Master_matrix::Option_list::column_type == Column_types::VECTOR) { - res.reserve(col.size()); - // TODO: have a better way to do this. For now, one cannot use the column iterators for that. - for (auto i : col.get_content()) { - if (i != 0) res.push_back(j); - ++j; + auto _substract_vec = [&](E& e, auto p, auto line) -> void { + if constexpr (Master_matrix::Option_list::is_z2) { + if (line[p]) e = !e; + } else { + if (line[p.first]) op->subtract_inplace_front(e, line[p.first] * p.second); } - } else { - res.resize(col.size()); - for (const auto& entry : col) { - res[j] = entry.get_row_index(); - ++j; + }; + + auto _multiply = [&](E& e, E m) -> void { + if constexpr (Master_matrix::Option_list::is_z2) { + e = m && e; // just in case, but m is only possibly 0 if the user multiplied by 0 a column in R + } else { + op->multiply_inplace(e, op->get_inverse(m)); } - } + }; - return res; -} + auto _assign = [&](E& e, const auto& cell) -> void { + if constexpr (Master_matrix::Option_list::is_z2) { + e = !e; + } else { + e = cell.get_element(); + } + }; -template -inline typename RU_representative_cycles::Cycle_border -RU_representative_cycles::_get_dependent_border(Index uIndex) { - if (u_transposed_[uIndex].size() == 1){ - return _get_border(uIndex); - } + auto _get_index = [&](auto resIt) { + if constexpr (Master_matrix::Option_list::is_z2) { + return *resIt; + } else { + return resIt->first; + } + }; - auto add = [](const Master_matrix::Column& col, Cycle_unreduced_borders_tmp& b){ - if constexpr (Master_matrix::Option_list::column_type == Column_types::HEAP || - Master_matrix::Option_list::column_type == Column_types::VECTOR) { - // TODO: have a better way to do this. For now, one cannot use the column iterators for that. - unsigned int j = 0; - for (auto k : col.get_content()) { - if (k != 0) { - auto p = b.insert(j); - if (!p.second) b.erase(p.first); - } - ++j; - } + auto _translate = [&](std::size_t i) -> void { + const auto& map = _matrix()->positionToID_; + if constexpr (Master_matrix::Option_list::is_z2) { + auto it = map.find(res[i]); + if (it != map.end()) res[i] = it->second; } else { - for (const auto& entry : col) { - auto p = b.insert(entry.get_row_index()); - if (!p.second) b.erase(p.first); - } + auto it = map.find(res[i].first); + if (it != map.end()) res[i].first = it->second; } }; - Cycle_unreduced_borders_tmp b; - for (Index i : u_transposed_[uIndex]){ - add(_matrix()->reducedMatrixR_.get_column(i), b); + if (c == size - 1) _push_cell(size - 1, _last_diagonal_value()); + for (int i = size - 2; i >= 0; --i) { + E e = static_cast(c) == i; + // ugly...... + if constexpr (is_well_behaved::value) { + const auto& line = matrix.get_column(i); + auto resIt = res.rbegin(); + auto lineIt = line.begin(); + E diag(0); + if (static_cast(lineIt->get_row_index()) == i) { + _assign(diag, *lineIt); + ++lineIt; + } + while (lineIt != line.end() && resIt != res.rend()) { + while (resIt != res.rend() && _get_index(resIt) < lineIt->get_row_index()) ++resIt; + _substract(e, resIt, *lineIt); + ++lineIt; + } + _multiply(e, diag); + } else { + auto line = matrix.get_column(i).get_content(size); // linear... + for (const auto& p : res) { + _substract_vec(e, p, line); + } + _multiply(e, line[i]); + } + _push_cell(i, e); } - Cycle_border res(b.begin(), b.end()); - std::sort(res.begin(), res.end()); + // reverse order + PosIdx to IDIdx translation + for (std::size_t incr = 0, decr = res.size() - 1; incr < decr; ++incr, --decr) { + _translate(incr); + _translate(decr); + std::swap(res[incr], res[decr]); + } return res; } +template +inline void RU_representative_cycles::_reset() +{ + representativeCycles_.clear(); + birthToCycle_.clear(); +} + } // namespace persistence_matrix } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h b/multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h index 00c76749..d073a770 100644 --- a/multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h +++ b/multipers/gudhi/gudhi/Persistence_matrix/ru_vine_swap.h @@ -19,13 +19,11 @@ #ifndef PM_RU_VINE_SWAP_H #define PM_RU_VINE_SWAP_H -#include //std::move -#include //std::conditional +#include //std::move #include -#include //std::invalid_argument +#include //std::invalid_argument -#include "ru_pairing.h" -#include "boundary_cell_position_to_id_mapper.h" +#include namespace Gudhi { namespace persistence_matrix { @@ -36,9 +34,10 @@ namespace persistence_matrix { * @brief Empty structure. * Inherited instead of @ref RU_vine_swap, when vine swaps are not enabled. */ -struct Dummy_ru_vine_swap -{ - friend void swap([[maybe_unused]] Dummy_ru_vine_swap& d1, [[maybe_unused]] Dummy_ru_vine_swap& d2) {} +struct Dummy_ru_vine_swap { + using RUP = void; + + friend void swap([[maybe_unused]] Dummy_ru_vine_swap& d1, [[maybe_unused]] Dummy_ru_vine_swap& d2) noexcept {} }; /** @@ -47,57 +46,157 @@ struct Dummy_ru_vine_swap * @brief Empty structure. * Inherited instead of @ref RU_pairing, when the barcode is not stored. */ -struct Dummy_ru_vine_pairing -{ - friend void swap([[maybe_unused]] Dummy_ru_vine_pairing& d1, [[maybe_unused]] Dummy_ru_vine_pairing& d2) {} +struct Dummy_ru_vine_pairing { + friend void swap([[maybe_unused]] Dummy_ru_vine_pairing& d1, [[maybe_unused]] Dummy_ru_vine_pairing& d2) noexcept {} }; /** - * @class RU_vine_swap ru_vine_swap.h gudhi/Persistence_matrix/ru_vine_swap.h * @ingroup persistence_matrix * - * @brief Class managing the vine swaps for @ref RU_matrix. - * + * @brief Class managing the barcode for @ref RU_vine_swap. + * * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. */ -template -class RU_vine_swap : public std::conditional, - Dummy_ru_vine_pairing - >::type, - public std::conditional - >::type +template +class RU_barcode_swap : public RU_pairing { public: - using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ - using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ - using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + // RUP = RU Pairing + using RUP = RU_pairing; /** * @brief Default constructor. */ - RU_vine_swap(); + RU_barcode_swap() = default; /** * @brief Copy constructor. - * - * @param matrixToCopy Matrix to copy. + * + * @param toCopy Matrix to copy. */ - RU_vine_swap(const RU_vine_swap& matrixToCopy); + RU_barcode_swap(const RU_barcode_swap& toCopy) : RUP(static_cast(toCopy)) {}; /** * @brief Move constructor. - * + * * @param other Matrix to move. */ - RU_vine_swap(RU_vine_swap&& other) noexcept; + RU_barcode_swap(RU_barcode_swap&& other) noexcept : RUP(std::move(static_cast(other))) {}; + + ~RU_barcode_swap() = default; + + RU_barcode_swap& operator=(const RU_barcode_swap& other) + { + RUP::operator=(other); + return *this; + } + + RU_barcode_swap& operator=(RU_barcode_swap&& other) noexcept + { + RUP::operator=(std::move(other)); + return *this; + } + + friend void swap(RU_barcode_swap& swap1, RU_barcode_swap& swap2) noexcept + { + swap(static_cast(swap1), static_cast(swap2)); + } + + protected: + void _positive_transpose_barcode(Index columnIndex) + { + _birth(columnIndex) = columnIndex + 1; + _birth(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } + + void _negative_transpose_barcode(Index columnIndex) + { + _death(columnIndex) = columnIndex + 1; + _death(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } + + void _positive_negative_transpose_barcode(Index columnIndex) + { + _birth(columnIndex) = columnIndex + 1; + _death(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } + + void _negative_positive_transpose_barcode(Index columnIndex) + { + _death(columnIndex) = columnIndex + 1; + _birth(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } + + Pos_index _death_val(Pos_index index) const + { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(index)->death; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(index)).death; + } + } + + Pos_index _birth_val(Pos_index index) const + { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(index)->birth; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(index)).birth; + } + } + + void _reset() { RUP::_reset(); } + + private: + Pos_index& _death(Pos_index index) + { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(index)->death; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(index)).death; + } + } + + Pos_index& _birth(Pos_index index) + { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(index)->birth; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(index)).birth; + } + } +}; + +/** + * @class RU_vine_swap ru_vine_swap.h gudhi/Persistence_matrix/ru_vine_swap.h + * @ingroup persistence_matrix + * + * @brief Class managing the vine swaps for @ref RU_matrix. + * + * @tparam Master_matrix An instantiation of @ref Matrix from which all types and options are deduced. + */ +template +class RU_vine_swap +{ + public: + using Index = typename Master_matrix::Index; /**< @ref MatIdx index type. */ + using ID_index = typename Master_matrix::ID_index; /**< @ref IDIdx index type. */ + using Pos_index = typename Master_matrix::Pos_index; /**< @ref PosIdx index type. */ + + /** + * @brief Default constructor. + */ + RU_vine_swap() = default; /** * @brief Does the same than @ref vine_swap, but assumes that the swap is non trivial and * therefore skips a part of the case study. - * + * * @param index @ref PosIdx index of the first cell to swap. The second one has to be at `position + 1`. * @return true If the barcode changed from the swap. * @return false Otherwise. @@ -110,49 +209,22 @@ class RU_vine_swap : public std::conditional&>(swap1), static_cast&>(swap2)); - } - if (!Master_matrix::Option_list::has_column_pairings || !Master_matrix::Option_list::has_removable_columns) { - swap(static_cast&>(swap1), - static_cast&>(swap2)); - } - } - - protected: - //RUP = RU matrix Pairing - using RUP = typename std::conditional, - Dummy_ru_vine_pairing - >::type; - //RUM = RU matrix position to id Map - using RUM = typename std::conditional - >::type; - constexpr auto& _positionToRowIdx(); + friend void swap([[maybe_unused]] RU_vine_swap& swap1, [[maybe_unused]] RU_vine_swap& swap2) noexcept {} private: using Master_RU_matrix = typename Master_matrix::Master_RU_matrix; bool _is_paired(Index columnIndex); - void _swap_at_index(Index columnIndex); void _add_to(Index sourceIndex, Index targetIndex); void _positive_transpose(Index columnIndex); @@ -163,36 +235,17 @@ class RU_vine_swap : public std::conditional(this); } + constexpr const Master_RU_matrix* _matrix() const { return static_cast(this); } }; template -inline RU_vine_swap::RU_vine_swap() : RUP(), RUM() -{} - -template -inline RU_vine_swap::RU_vine_swap(const RU_vine_swap& matrixToCopy) - : RUP(static_cast(matrixToCopy)), - RUM(static_cast(matrixToCopy)) -{} - -template -inline RU_vine_swap::RU_vine_swap(RU_vine_swap&& other) noexcept - : RUP(std::move(static_cast(other))), - RUM(std::move(static_cast(other))) -{} - -template -inline bool RU_vine_swap::vine_swap_with_z_eq_1_case(Pos_index index) +inline bool RU_vine_swap::vine_swap_with_z_eq_1_case(Pos_index index) { GUDHI_CHECK(index < _matrix()->reducedMatrixR_.get_number_of_columns() - 1, std::invalid_argument("RU_vine_swap::vine_swap_with_z_eq_1_case - Index to swap out of bound.")); @@ -203,17 +256,18 @@ inline bool RU_vine_swap::vine_swap_with_z_eq_1_case(Pos_index in if (iIsPositive && iiIsPositive) { _matrix()->mirrorMatrixU_.zero_entry(index, _get_row_id_from_position(index + 1)); return _positive_vine_swap(index); - } else if (!iIsPositive && !iiIsPositive) { + } + if (!iIsPositive && !iiIsPositive) { return _negative_vine_swap(index); - } else if (iIsPositive && !iiIsPositive) { + } + if (iIsPositive && !iiIsPositive) { return _positive_negative_vine_swap(index); - } else { - return _negative_positive_vine_swap(index); } + return _negative_positive_vine_swap(index); } template -inline bool RU_vine_swap::vine_swap(Pos_index index) +inline bool RU_vine_swap::vine_swap(Pos_index index) { GUDHI_CHECK(index < _matrix()->reducedMatrixR_.get_number_of_columns() - 1, std::invalid_argument("RU_vine_swap::vine_swap - Index to swap out of bound.")); @@ -232,7 +286,8 @@ inline bool RU_vine_swap::vine_swap(Pos_index index) _matrix()->mirrorMatrixU_.zero_entry(index, _get_row_id_from_position(index + 1)); } return _positive_vine_swap(index); - } else if (!iIsPositive && !iiIsPositive) { + } + if (!iIsPositive && !iiIsPositive) { if (_matrix()->reducedMatrixR_.get_column_dimension(index) != _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || _matrix()->mirrorMatrixU_.is_zero_entry(index, _get_row_id_from_position(index + 1))) { @@ -241,7 +296,8 @@ inline bool RU_vine_swap::vine_swap(Pos_index index) return true; } return _negative_vine_swap(index); - } else if (iIsPositive && !iiIsPositive) { + } + if (iIsPositive && !iiIsPositive) { if (_matrix()->reducedMatrixR_.get_column_dimension(index) != _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || _matrix()->mirrorMatrixU_.is_zero_entry(index, _get_row_id_from_position(index + 1))) { @@ -250,38 +306,29 @@ inline bool RU_vine_swap::vine_swap(Pos_index index) return true; } return _positive_negative_vine_swap(index); - } else { - if (_matrix()->reducedMatrixR_.get_column_dimension(index) != - _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || - _matrix()->mirrorMatrixU_.is_zero_entry(index, _get_row_id_from_position(index + 1))) { - _negative_positive_transpose(index); - _swap_at_index(index); - return true; - } - return _negative_positive_vine_swap(index); } + if (_matrix()->reducedMatrixR_.get_column_dimension(index) != + _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || + _matrix()->mirrorMatrixU_.is_zero_entry(index, _get_row_id_from_position(index + 1))) { + _negative_positive_transpose(index); + _swap_at_index(index); + return true; + } + return _negative_positive_vine_swap(index); } template -inline RU_vine_swap& RU_vine_swap::operator=(RU_vine_swap other) -{ - RUP::operator=(other); - RUM::operator=(other); - return *this; -} - -template -inline bool RU_vine_swap::_is_paired(Index columnIndex) +inline bool RU_vine_swap::_is_paired(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - return _get_death(columnIndex) != static_cast(-1); + return _get_death(columnIndex) != Master_matrix::template get_null_value(); } else { if (!_matrix()->reducedMatrixR_.is_zero_column(columnIndex)) return true; if constexpr (Master_matrix::Option_list::has_map_column_container) { if (_matrix()->pivotToColumnIndex_.find(columnIndex) == _matrix()->pivotToColumnIndex_.end()) return false; } else { - if (_matrix()->pivotToColumnIndex_.operator[](columnIndex) == static_cast(-1)) return false; + if (_matrix()->pivotToColumnIndex_[columnIndex] == Master_matrix::template get_null_value()) return false; } return true; @@ -300,14 +347,14 @@ inline void RU_vine_swap::_swap_at_index(Index columnIndex) } template -inline void RU_vine_swap::_add_to(Index sourceIndex, Index targetIndex) +inline void RU_vine_swap::_add_to(Index sourceIndex, Index targetIndex) { _matrix()->reducedMatrixR_.add_to(sourceIndex, targetIndex); _matrix()->mirrorMatrixU_.add_to(targetIndex, sourceIndex); } template -inline void RU_vine_swap::_positive_transpose(Index columnIndex) +inline void RU_vine_swap::_positive_transpose(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_map_column_container) { if (_is_paired(columnIndex) && _is_paired(columnIndex + 1)) { @@ -320,31 +367,26 @@ inline void RU_vine_swap::_positive_transpose(Index columnIndex) _matrix()->pivotToColumnIndex_.erase(columnIndex + 1); } } else { - std::swap(_matrix()->pivotToColumnIndex_.operator[](columnIndex), - _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1)); + std::swap(_matrix()->pivotToColumnIndex_[columnIndex], _matrix()->pivotToColumnIndex_[columnIndex + 1]); } if constexpr (Master_matrix::Option_list::has_column_pairings) { - _birth(columnIndex) = columnIndex + 1; - _birth(columnIndex + 1) = columnIndex; - std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + _matrix()->_positive_transpose_barcode(columnIndex); } } template -inline void RU_vine_swap::_negative_transpose(Index columnIndex) +inline void RU_vine_swap::_negative_transpose(Index columnIndex) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - _death(columnIndex) = columnIndex + 1; - _death(columnIndex + 1) = columnIndex; - std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + _matrix()->_negative_transpose_barcode(columnIndex); } std::swap(_matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex)), _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex + 1))); } template -inline void RU_vine_swap::_positive_negative_transpose(Index columnIndex) +inline void RU_vine_swap::_positive_negative_transpose(Index columnIndex) { _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex + 1)) = columnIndex; if constexpr (Master_matrix::Option_list::has_map_column_container) { @@ -353,19 +395,17 @@ inline void RU_vine_swap::_positive_negative_transpose(Index colu _matrix()->pivotToColumnIndex_.erase(columnIndex); } } else { - _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1) = _matrix()->pivotToColumnIndex_.operator[](columnIndex); - _matrix()->pivotToColumnIndex_.operator[](columnIndex) = -1; + _matrix()->pivotToColumnIndex_[columnIndex + 1] = _matrix()->pivotToColumnIndex_[columnIndex]; + _matrix()->pivotToColumnIndex_[columnIndex] = Master_matrix::template get_null_value(); } if constexpr (Master_matrix::Option_list::has_column_pairings) { - _birth(columnIndex) = columnIndex + 1; - _death(columnIndex + 1) = columnIndex; - std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + _matrix()->_positive_negative_transpose_barcode(columnIndex); } } template -inline void RU_vine_swap::_negative_positive_transpose(Index columnIndex) +inline void RU_vine_swap::_negative_positive_transpose(Index columnIndex) { _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex)) = columnIndex + 1; if constexpr (Master_matrix::Option_list::has_map_column_container) { @@ -374,24 +414,23 @@ inline void RU_vine_swap::_negative_positive_transpose(Index colu _matrix()->pivotToColumnIndex_.erase(columnIndex + 1); } } else { - _matrix()->pivotToColumnIndex_.operator[](columnIndex) = _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1); - _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1) = -1; + _matrix()->pivotToColumnIndex_[columnIndex] = _matrix()->pivotToColumnIndex_[columnIndex + 1]; + _matrix()->pivotToColumnIndex_[columnIndex + 1] = Master_matrix::template get_null_value(); } if constexpr (Master_matrix::Option_list::has_column_pairings) { - _death(columnIndex) = columnIndex + 1; - _birth(columnIndex + 1) = columnIndex; - std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + _matrix()->_negative_positive_transpose_barcode(columnIndex); } } template -inline bool RU_vine_swap::_positive_vine_swap(Index columnIndex) +inline bool RU_vine_swap::_positive_vine_swap(Index columnIndex) { const Pos_index iDeath = _get_death(columnIndex); const Pos_index iiDeath = _get_death(columnIndex + 1); - if (iDeath != static_cast(-1) && iiDeath != static_cast(-1) && + if (iDeath != Master_matrix::template get_null_value() && + iiDeath != Master_matrix::template get_null_value() && !(_matrix()->reducedMatrixR_.is_zero_entry(iiDeath, _get_row_id_from_position(columnIndex)))) { if (iDeath < iiDeath) { _swap_at_index(columnIndex); @@ -407,7 +446,8 @@ inline bool RU_vine_swap::_positive_vine_swap(Index columnIndex) _swap_at_index(columnIndex); - if (iDeath != static_cast(-1) || iiDeath == static_cast(-1) || + if (iDeath != Master_matrix::template get_null_value() || + iiDeath == Master_matrix::template get_null_value() || _matrix()->reducedMatrixR_.is_zero_entry(iiDeath, _get_row_id_from_position(columnIndex + 1))) { _positive_transpose(columnIndex); return true; @@ -416,7 +456,7 @@ inline bool RU_vine_swap::_positive_vine_swap(Index columnIndex) } template -inline bool RU_vine_swap::_negative_vine_swap(Index columnIndex) +inline bool RU_vine_swap::_negative_vine_swap(Index columnIndex) { const Pos_index iBirth = _get_birth(columnIndex); const Pos_index iiBirth = _get_birth(columnIndex + 1); @@ -435,7 +475,7 @@ inline bool RU_vine_swap::_negative_vine_swap(Index columnIndex) } template -inline bool RU_vine_swap::_positive_negative_vine_swap(Index columnIndex) +inline bool RU_vine_swap::_positive_negative_vine_swap(Index columnIndex) { _matrix()->mirrorMatrixU_.zero_entry(columnIndex, _get_row_id_from_position(columnIndex + 1)); @@ -446,72 +486,40 @@ inline bool RU_vine_swap::_positive_negative_vine_swap(Index colu } template -inline bool RU_vine_swap::_negative_positive_vine_swap(Index columnIndex) +inline bool RU_vine_swap::_negative_positive_vine_swap(Index columnIndex) { _add_to(columnIndex, columnIndex + 1); // useless for R? - _swap_at_index(columnIndex); // if additions not made for R, do not swap R columns, just rows + _swap_at_index(columnIndex); // if additions not made for R, do not swap R columns, just rows _add_to(columnIndex, columnIndex + 1); // useless for R? return false; } template -inline typename RU_vine_swap::Pos_index& RU_vine_swap::_death(Pos_index simplexIndex) -{ - static_assert(Master_matrix::Option_list::has_column_pairings, "Pairing necessary to modify death value."); - - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return RUP::indexToBar_.at(simplexIndex)->death; - } else { - return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).death; - } -} - -template -inline typename RU_vine_swap::Pos_index& RU_vine_swap::_birth(Pos_index simplexIndex) -{ - static_assert(Master_matrix::Option_list::has_column_pairings, "Pairing necessary to modify birth value."); - - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return RUP::indexToBar_.at(simplexIndex)->birth; - } else { - return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).birth; - } -} - -template -inline typename RU_vine_swap::Pos_index RU_vine_swap::_get_death(Index simplexIndex) +inline typename RU_vine_swap::Pos_index RU_vine_swap::_get_death(Index simplexIndex) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return RUP::indexToBar_.at(simplexIndex)->death; - } else { - return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).death; - } + return _matrix()->_death_val(simplexIndex); } else { if (!_matrix()->reducedMatrixR_.is_zero_column(simplexIndex)) return _matrix()->reducedMatrixR_.get_column(simplexIndex).get_pivot(); if constexpr (Master_matrix::Option_list::has_map_column_container) { auto it = _matrix()->pivotToColumnIndex_.find(simplexIndex); - if (it == _matrix()->pivotToColumnIndex_.end()) return -1; + if (it == _matrix()->pivotToColumnIndex_.end()) return Master_matrix::template get_null_value(); return it->second; } else { - return _matrix()->pivotToColumnIndex_.operator[](simplexIndex); + return _matrix()->pivotToColumnIndex_[simplexIndex]; } } } template inline typename RU_vine_swap::Pos_index RU_vine_swap::_get_birth( - Index negativeSimplexIndex) + Index negativeSimplexIndex) { if constexpr (Master_matrix::Option_list::has_column_pairings) { - if constexpr (Master_matrix::Option_list::has_removable_columns) { - return RUP::indexToBar_.at(negativeSimplexIndex)->birth; - } else { - return RUP::barcode_.at(RUP::indexToBar_.at(negativeSimplexIndex)).birth; - } + return _matrix()->_birth_val(negativeSimplexIndex); } else { return _matrix()->reducedMatrixR_.get_pivot(negativeSimplexIndex); } @@ -519,19 +527,11 @@ inline typename RU_vine_swap::Pos_index RU_vine_swap inline typename RU_vine_swap::ID_index RU_vine_swap::_get_row_id_from_position( - Pos_index position) -{ - auto it = _positionToRowIdx().find(position); - return it == _positionToRowIdx().end() ? position : it->second; -} - -template -inline constexpr auto& RU_vine_swap::_positionToRowIdx() + Pos_index position) const { - if constexpr (Master_matrix::Option_list::has_column_pairings && Master_matrix::Option_list::has_removable_columns) - return RUP::PIDM::map_; - else - return RUM::map_; + const auto& map = _matrix()->positionToID_; + auto it = map.find(position); + return it == map.end() ? position : it->second; } } // namespace persistence_matrix diff --git a/multipers/gudhi/gudhi/Persistence_on_a_line.h b/multipers/gudhi/gudhi/Persistence_on_a_line.h new file mode 100644 index 00000000..120dea99 --- /dev/null +++ b/multipers/gudhi/gudhi/Persistence_on_a_line.h @@ -0,0 +1,152 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Marc Glisse + * + * Copyright (C) 2022 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef PERSISTENCE_ON_A_LINE_H_ +#define PERSISTENCE_ON_A_LINE_H_ + +#include +#include +#include +#include +#include + +namespace Gudhi::persistent_cohomology { +/** + * \brief Computes the persistent homology of the sublevelsets of a PL function defined on \f$\mathbb{R}\f$ in linear time. + * + * \ingroup persistent_cohomology + * + * Not all input values appear in the output (they would be part of pairs of length 0 + * with a simplicial / cubical complex). + * + * @param[in] input Range of filtration values. + * @param[out] out Functor that is called as `out(birth, death)` for each persistence pair. + * By convention, it is also called one last time on the minimum and `std::numeric_limits::infinity()`. + * @param[in] lt Functor that compares 2 filtration values. + * + * \author Marc Glisse + */ +template> +void compute_persistence_of_function_on_line(FiltrationRange const& input, OutputFunctor&& out, Compare&& lt = {}) { + // We process the elements of input 1 by 1, simplifying and outputting as much as possible. + // The simplified sequence with the elements that are still active is stored in data. + // Invariant: data contains a sequence of type 1 9 2 8 3 7 ... + using std::begin; + using std::end; + auto it = begin(input); + auto stop = end(input); + if (it == stop) return; + typedef std::decay_t Filtration; + std::vector data; + Filtration v; + auto le = [<](auto& x, auto& y){ return !lt(y, x); }; + auto ge = [<](auto& x, auto& y){ return !lt(x, y); }; + auto gt = [<](auto& x, auto& y){ return lt(y, x); }; + data.push_back(*it++); +state1: // data contains a single element + if (it == stop) goto infinite; + v = *it++; + if (le(v, data[0])) { +state1down: + data[0] = v; + goto state1; + } + data.push_back(v); + goto state12; +state12: // data contains only 2 elements, necessarily data[0] < data[1] + if (it == stop) goto endup; + v = *it++; + if (ge(v, data[1])) { + data[1] = v; + goto state12; + } +state12down: + if (le(v, data[0])) { + out(data[0], data[1]); + data.pop_back(); + data[0] = v; + goto state1; + } + data.push_back(v); + goto state132; +state132: // data[-3] < data[-1] < data[-2] + if (it == stop) goto enddown; + v = *it++; + if (le(v, data.back())) { + if (gt(v,data.end()[-3])) { + data.back() = v; goto state132; + } else { + out(data.end()[-3], data.end()[-2]); + data.erase(data.end()-3, data.end()); + if (data.empty()) { data.push_back(v); goto state1; } + goto down; + } + } else { +state132up: + if (ge(v, data.end()[-2])) { + out(data.end()[-1], data.end()[-2]); + data.erase(data.end()-2, data.end()); + goto up; + } else { + data.push_back(v); + goto state312; + } + } +state312: // data[-2] < data[-1] < data[-3] + if (it == stop) goto endup; + v = *it++; + if (ge(v, data.back())) { + if (lt(v,data.end()[-3])) { + data.back() = v; goto state312; + } else { + out(data.end()[-2], data.end()[-3]); + data.erase(data.end()-3, data.end()); + GUDHI_CHECK (!data.empty(), std::logic_error("Bug in Gudhi")); + goto up; + } + } else { +state312down: + if (le(v, data.end()[-2])) { + out(data.end()[-2], data.end()[-1]); + data.erase(data.end()-2, data.end()); + goto down; + } else { + data.push_back(v); + goto state132; + } + } +up: // data[-1] < v after a simplification + if (data.size() == 1) { data.push_back(v); goto state12; } + goto state132up; +down: // v < data[-1] after a simplification + switch (data.size()) { + case 1: + goto state1down; + case 2: + goto state12down; + default: + goto state312down; + } + // From here on, we have finished reading input +endup: // data[-2] < data[-1] + data.pop_back(); + goto enddown; +enddown: // data[-1] < data[-2] + if (data.size() > 1) { + out(data.end()[-1], data.end()[-2]); + data.erase(data.end()-2, data.end()); + goto enddown; + } + goto infinite; +infinite: // data only contains the global minimum + out(data[0], std::numeric_limits::infinity()); +} +} // namespace Gudhi::persistent_cohomology +#endif diff --git a/multipers/gudhi/gudhi/Persistence_on_rectangle.h b/multipers/gudhi/gudhi/Persistence_on_rectangle.h new file mode 100644 index 00000000..32061238 --- /dev/null +++ b/multipers/gudhi/gudhi/Persistence_on_rectangle.h @@ -0,0 +1,617 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Marc Glisse + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +// ds_find_set_ is inspired from code in Boost.Graph that is +// +// (C) Copyright Jeremy Siek 2004 +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + + +#ifndef PERSISTENCE_ON_RECTANGLE_H +#define PERSISTENCE_ON_RECTANGLE_H + +#include +#ifdef GUDHI_DETAILED_TIMES + #include +#endif + +#include + +#ifdef GUDHI_USE_TBB + #include +#endif + +#ifdef DEBUG_TRACES + #include +#endif +#include +#include +#include +#include +#include + +namespace Gudhi::cubical_complex { + +// When building a cubical complex from top-dimensional cells, there are +// normally more vertices than input top cells ((x+1)*(y+1) instead of x*y). +// However, the top cells along the boundary turn out to be collapsible, and we +// only need to work with (x-1)*(y-1) vertices. +template +struct Persistence_on_rectangle { + // If we want to save space, we don't have to store the redundant 'first' + // field in T_with_index. However, it would slow down the primal pass. + struct T_with_index { + Filtration_value first; Index second; + T_with_index() = default; + T_with_index(Filtration_value f, Index i) : first(f), second(i) {} + bool operator<(T_with_index const& other) const { + return std::tie(first, second) < std::tie(other.first, other.second); + } + Index out() const { return second; } + }; + // Don't store the index if we don't want to output it. + struct T_no_index { + Filtration_value first; + T_no_index() = default; + T_no_index(Filtration_value f, Index) : first(f) {} + bool operator<(T_no_index const& other) const { return first < other.first; } + Filtration_value out() const { return first; } + }; + typedef std::conditional_t T; + + Filtration_value const* input_p; + Filtration_value input(Index i) const { return input_p[i]; } + + // size_* counts the number of vertices in each direction. + Index size_x, size_y, input_size; + // The square i + dy is right above i. + Index dy; + + // Squares keep their index from the input. + // Vertices have the same index as the square at their bottom left (smaller x and y) + // Store the filtration value of vertices that could be critical. We could store them + // in some map, or recompute them on demand, but this strongly affects performance. + std::unique_ptr data_v_; + T& data_vertex(Index i){ return data_v_[i]; } + T data_vertex(Index i) const { return data_v_[i]; } + + std::conditional_t global_min; + + // Information on a cluster + // We do not use the rank/size heuristics, they do not go well with the pre-pairing and end up slowing things down. + // We thus use the same representative for disjoint-sets and persistence (the minimum). + std::unique_ptr ds_parent_v_; + std::vector ds_parent_s_; + Index& ds_parent_vertex(Index n) { return ds_parent_v_[n]; } + Index& ds_parent_square(Index n) { return ds_parent_s_[n]; } + + template + Index ds_find_set_(Index v, Parent&&ds_parent) { + // Experimentally, path halving is currently the fastest. Note that with a + // different algorithm, full compression was faster, so make sure to check + // again if the algorithm changes. + // (the setting is unusual because we start from a forest with broken ranks) +#if 0 + // Full compression + Index old = v; + Index ancestor = ds_parent(v); + while (ancestor != v) + { + v = ancestor; + ancestor = ds_parent(v); + } + v = ds_parent(old); + while (ancestor != v) + { + ds_parent(old) = ancestor; + old = v; + v = ds_parent(old); + } + return ancestor; +#elif 1 + // Path halving + Index parent = ds_parent(v); + Index grandparent = ds_parent(parent); + while (parent != grandparent) + { + ds_parent(v) = grandparent; + v = grandparent; + parent = ds_parent(v); + grandparent = ds_parent(parent); + } + return parent; +#elif 1 + // Path splitting + Index parent = ds_parent(v); + Index grandparent = ds_parent(parent); + while (parent != grandparent) + { + ds_parent(v) = grandparent; + v = parent; + parent = grandparent; + grandparent = ds_parent(parent); + } + return parent; +#elif 1 + // No compression (just for reference) + Index parent; + while (v != (parent = ds_parent(v))) + v = parent; + return v; +#endif + } + Index ds_find_set_vertex(Index v) { + return ds_find_set_(v, [this](Index i) -> Index& { return ds_parent_vertex(i); }); + } + Index ds_find_set_square(Index v) { + return ds_find_set_(v, [this](Index i) -> Index& { return ds_parent_square(i); }); + } + + struct Edge { + T f; + Index v1, v2; // v1 < v2 + Edge() = default; + Edge(T f, Index v1, Index v2) : f(f), v1(v1), v2(v2) {} + Filtration_value filt() const { return f.first; } + bool operator<(Edge const& other) const { return filt() < other.filt(); } + }; + void dualize_edge(Edge& e) const { + Index new_v2 = e.v1 + (dy + 1); + e.v1 = e.v2; + e.v2 = new_v2; + }; + std::vector edges; + + void init(const Filtration_value* input_, Index n_rows, Index n_cols) { + input_size = n_rows * n_cols; + input_p = input_; +#ifdef DEBUG_TRACES + std::clog << "Input\n"; + for(Index i = 0; i < input_size; ++i) { + std::clog << i << '\t' << input(i) << '\n'; + } +#endif + dy = n_cols; + size_x = dy - 1; + size_y = n_rows - 1; + // The unique_ptr could be std::vector, but the initialization is useless. + data_v_.reset(new T[input_size - dy - 1]); // 1 row/column less for vertices than squares + ds_parent_v_.reset(new Index[input_size - dy - 1]); + // Initializing the boundary squares to 0 is important, it represents the infinite exterior cell. + ds_parent_s_.resize(input_size); + // What is a good estimate here? For a random 1000x1000 input, we get ~311k edges. For a checkerboard, ~498k. + edges.reserve(input_size / 2); + } + + bool has_larger_input(Index a, Index b, Filtration_value fb) const { + // Is passing fb useful, or would the compiler notice that it already has it available? + GUDHI_CHECK(a != b, std::logic_error("Bug in Gudhi: comparing a cell to itself")); + Filtration_value fa = input(a); + if (fb < fa) return true; + if (fa < fb) return false; + return a > b; // Arbitrary, but has to be consistent + } + void set_parent_vertex(Index child, Index parent) { + GUDHI_CHECK(child != parent, std::logic_error("Bug in Gudhi: use mark_*_critical instead of set_parent")); + ds_parent_vertex(child) = parent; + } + void set_parent_square(Index child, Index parent) { + GUDHI_CHECK(child != parent, std::logic_error("Bug in Gudhi: use mark_*_critical instead of set_parent")); + ds_parent_square(child) = parent; + } + + // Locally pair simplices around each square. + // Work implicitly from input, only store the filtration value of critical vertices (squares are already in input). + // Store critical edges for later processing. + void fill_and_pair() { + Index i; // Index of the current square + Filtration_value f; // input(i) + auto mark_vertex_critical = [&](Index c) { + ds_parent_vertex(c) = c; + data_vertex(c) = T(f, i); + }; + auto mark_square_critical = [&]() { + ds_parent_square(i) = i; + }; + auto mark_edge_critical = [&](Index v1, Index v2) { + edges.emplace_back(T(f, i), v1, v2); + }; + auto v_up_left = [&](){ return i - 1; }; + auto v_up_right = [&](){ return i; }; + auto v_down_left = [&](){ return i - dy - 1; }; + auto v_down_right = [&](){ return i - dy; }; + auto pair_square_up = [&](){ set_parent_square(i, i + dy); }; + auto pair_square_down = [&](){ set_parent_square(i, i - dy); }; + auto pair_square_left = [&](){ set_parent_square(i, i - 1); }; + auto pair_square_right = [&](){ set_parent_square(i, i + 1); }; + + // Mark the corners as critical, it will be overwritten if not + i = 0; f = input(i); + mark_vertex_critical(v_up_right()); + i = size_x; f = input(i); + mark_vertex_critical(v_up_left()); + i = dy * size_y; f = input(i); + mark_vertex_critical(v_down_right()); + i = size_x + dy * size_y; f = input(i); + mark_vertex_critical(v_down_left()); + + // Boundary nodes, 1st row + for(Index x = 1; x < size_x; ++x) { + i = x; + f = input(x); + if (has_larger_input(i + dy, i, f)) { + auto up_left = [&](){ return has_larger_input(i - 1, i, f) && has_larger_input(i + dy - 1, i, f); }; + auto up_right = [&](){ return has_larger_input(i + 1, i, f) && has_larger_input(i + dy + 1, i, f); }; + if (up_left()) { + set_parent_vertex(v_up_left(), v_up_right()); + if (up_right()) mark_vertex_critical(v_up_right()); + } else if (up_right()) { + set_parent_vertex(v_up_right(), v_up_left()); + } else { + mark_edge_critical(v_up_left(), v_up_right()); + } + } + } + // Internal rows + for(Index y = 1; y < size_y; ++y) { + // First column + { + i = y * dy; + f = input(i); + if (has_larger_input(i + 1, i, f)) { + auto down_right = [&](){ return has_larger_input(i - dy, i, f) && has_larger_input(i + 1 - dy, i, f); }; + auto up_right = [&](){ return has_larger_input(i + dy, i, f) && has_larger_input(i + 1 + dy, i, f); }; + if (down_right()) { + set_parent_vertex(v_down_right(), v_up_right()); + if (up_right()) mark_vertex_critical(v_up_right()); + } else if (up_right()) { + set_parent_vertex(v_up_right(), v_down_right()); + } else { + mark_edge_critical(v_down_right(), v_up_right()); + } + } + } + // Internal squares + for(Index x = 1; x < size_x; ++x) { + i = x + dy * y; + f = input(i); + // See what part of the boundary shares f + auto left = [&]() { return has_larger_input(i - 1, i, f); }; + auto right = [&]() { return has_larger_input(i + 1, i, f); }; + auto down = [&]() { return has_larger_input(i - dy, i, f); }; + auto up = [&]() { return has_larger_input(i + dy, i, f); }; + auto down_left = [&]() { return has_larger_input(i - dy - 1, i, f); }; + auto up_left = [&]() { return has_larger_input(i + dy - 1, i, f); }; + auto down_right = [&]() { return has_larger_input(i - dy + 1, i, f); }; + auto up_right = [&]() { return has_larger_input(i + dy + 1, i, f); }; + if (up()) { // u + if (left()) { // u l + if (up_left()) { // u l ul + set_parent_vertex(v_up_left(), v_up_right()); + if (down()) { // U l UL d + if (down_left()) { // U l UL d dl + set_parent_vertex(v_down_left(), v_up_left()); + if (right()) { // U L UL d DL r + if (down_right()) { // U L UL d DL r dr + set_parent_vertex(v_down_right(), v_down_left()); + pair_square_right(); + if (up_right()) { // U L UL D DL R DR ur - cr + mark_vertex_critical(v_up_right()); + } + } else { // U L UL d DL r !dr + pair_square_down(); + if (up_right()) { // U L UL D DL r !dr ur - cd + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U L UL D DL r !dr !ur - cd + mark_edge_critical(v_down_right(), v_up_right()); + } + } + } else { // U L UL d DL !r + pair_square_down(); + } + } else { // U l UL d !dl + pair_square_left(); + if (right()) { // U L UL d !dl r - cl + if (down_right()) { // U L UL d !dl r dr - cl + set_parent_vertex(v_down_right(), v_down_left()); + } else { // U L UL d !dl r !dr - cl + mark_edge_critical(v_down_left(), v_down_right()); + } + if (up_right()) { // U L UL D !dl r ur - cl + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U L UL D !dl r !ur - cl + mark_edge_critical(v_down_right(), v_up_right()); + } + } else { // U L UL d !dl !r - cl + mark_edge_critical(v_down_left(), v_down_right()); + } + } + } else { // U l UL !d + pair_square_left(); + if (right()) { // U L UL !d r - cl + if (up_right()) { // U L UL !d r ur - cl + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U L UL !d r !ur - cl + mark_edge_critical(v_down_right(), v_up_right()); + } + } else {} // U L UL !d !r - cl + } + } else { // u l !ul + pair_square_up(); + if (down()) { // U l !ul d - cu + if (down_left()) { // U l !ul d dl - cu + set_parent_vertex(v_down_left(), v_up_left()); + } else { // U l !ul d !dl - cu + mark_edge_critical(v_down_left(), v_up_left()); + } + if (right()) { // U L !ul d r - cu + if (down_right()) { // U L !ul d r dr - cu + set_parent_vertex(v_down_right(), v_down_left()); + } else { // U L !ul d r !dr - cu + mark_edge_critical(v_down_left(), v_down_right()); + } + if (up_right()) { // U L !ul D r ur - cu + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U L !ul D r !ur - cu + mark_edge_critical(v_down_right(), v_up_right()); + } + } else { // U L !ul d !r - cu + mark_edge_critical(v_down_left(), v_down_right()); + } + } else { // U l !ul !d - cu + mark_edge_critical(v_down_left(), v_up_left()); + if (right()) { // U L !ul !d r - cu + if (up_right()) { // U L !ul !d r ur - cu + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U L !ul !d r !ur - cu + mark_edge_critical(v_down_right(), v_up_right()); + } + } else {} // U L !ul !d !r - cu + } + } + } else { // u !l + pair_square_up(); + if (down()) { // U !l d - cu + if (right()) { // U !l d r - cu + if (down_right()) { // U !l d r dr - cu + set_parent_vertex(v_down_right(), v_down_left()); + } else { // U !l d r !dr - cu + mark_edge_critical(v_down_left(), v_down_right()); + } + if (up_right()) { // U !l D r ur - cu + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U !l D r !ur - cu + mark_edge_critical(v_down_right(), v_up_right()); + } + } else { // U !l d !r - cu + mark_edge_critical(v_down_left(), v_down_right()); + } + } else { // U !l !d - cu + if (right()) { // U !l !d r - cu + if (up_right()) { // U !l !d r ur - cu + set_parent_vertex(v_up_right(), v_down_right()); + } else { // U !l !d r !ur - cu + mark_edge_critical(v_down_right(), v_up_right()); + } + } else {} // U !l !d !r - cu + } + } + } else { // !u + if (left()) { // !u l + if (down()) { // !u l d + if (down_left()) { // !u l d dl + set_parent_vertex(v_down_left(), v_up_left()); + if (right()) { // !u L d DL r + if (down_right()) { // !u L d DL r dr + set_parent_vertex(v_down_right(), v_down_left()); + } else { // !u L d DL r !dr + mark_edge_critical(v_down_left(), v_down_right()); + } + pair_square_right(); + } else { // !u L d DL !r + pair_square_down(); + } + } else { // !u l d !dl + pair_square_left(); + if (right()) { // !u L d !dl r - cl + if (down_right()) { // !u L d !dl r dr - cl + set_parent_vertex(v_down_right(), v_down_left()); + } else { // !u L d !dl r !dr - cl + mark_edge_critical(v_down_left(), v_down_right()); + } + mark_edge_critical(v_down_right(), v_up_right()); + } else { // !u L d !dl !r - cl + mark_edge_critical(v_down_left(), v_down_right()); + } + } + } else { // !u l !d + pair_square_left(); + if (right()) { // !u L !d r - cl + mark_edge_critical(v_down_right(), v_up_right()); + } else {} // !u L !d !r - cl + } + } else { // !u !l + if (down()) { // !u !l d + pair_square_down(); + if (right()) { // !u !l D r - cd + if (down_right()) { // !u !l D r dr - cd + set_parent_vertex(v_down_right(), v_up_right()); + } else { // !u !l D r !dr - cd + mark_edge_critical(v_down_right(), v_up_right()); + } + } else {} // !u !l D !r - cd + } else { // !u !l !d + if (right()) { // !u !l !d r + pair_square_right(); + } else { // !u !l !d !r + mark_square_critical(); + } + } + } + } + } + // Last column + { + i = size_x + dy * y; + f = input(i); + if (has_larger_input(i - 1, i, f)) { + auto down_left = [&](){ return has_larger_input(i - dy, i, f) && has_larger_input(i - 1 - dy, i, f); }; + auto up_left = [&](){ return has_larger_input(i + dy, i, f) && has_larger_input(i - 1 + dy, i, f); }; + if (down_left()) { + set_parent_vertex(v_down_left(), v_up_left()); + if (up_left()) mark_vertex_critical(v_up_left()); + } else if (up_left()) { + set_parent_vertex(v_up_left(), v_down_left()); + } else { + mark_edge_critical(v_down_left(), v_up_left()); + } + } + } + } + // Boundary nodes, last row + for(Index x = 1; x < size_x; ++x) { + i = size_y * dy + x; + f = input(i); + if (has_larger_input(i - dy, i, f)) { + auto down_left = [&](){ return has_larger_input(i - 1, i, f) && has_larger_input(i - dy - 1, i, f); }; + auto down_right = [&](){ return has_larger_input(i + 1, i, f) && has_larger_input(i - dy + 1, i, f); }; + if (down_left()) { + set_parent_vertex(v_down_left(), v_down_right()); + if (down_right()) mark_vertex_critical(v_down_right()); + } else if (down_right()) { + set_parent_vertex(v_down_right(), v_down_left()); + } else { + mark_edge_critical(v_down_left(), v_down_right()); + } + } + } + } + + void sort_edges(){ +#ifdef GUDHI_USE_TBB + // Parallelizing just this part is a joke. It would be possible to + // parallelize the pairing (one edge list per thread) and run the dual in + // parallel with the primal if we were motivated... + tbb::parallel_sort(edges.begin(), edges.end()); +#else + std::sort(edges.begin(), edges.end()); +#endif +#ifdef DEBUG_TRACES + std::clog << "edges\n"; + for(auto&e : edges){ std::clog << e.v1 << '\t' << e.v2 << '\t' << e.filt() << '\n'; } +#endif + } + + template + void primal(Out&&out){ + auto it = std::remove_if(edges.begin(), edges.end(), [&](Edge& e) { + assert(e.v1 < e.v2); + Index a = ds_find_set_vertex(e.v1); + Index b = ds_find_set_vertex(e.v2); + if (a == b) return false; + if (data_vertex(b) < data_vertex(a)) std::swap(a, b); + ds_parent_vertex(b) = a; + out(data_vertex(b).out(), e.f.out()); + return true; + }); + edges.erase(it, edges.end()); + global_min = data_vertex(ds_find_set_vertex(0)).out(); + } + + // In the dual, squares behave like vertices, and edges are rotated 90° around their middle. + // To handle boundaries correctly, we imagine a single exterior cell with filtration +inf. + template + void dual(Out&&out){ + for (auto e : boost::adaptors::reverse(edges)) { + dualize_edge(e); + Index a = ds_find_set_square(e.v1); + Index b = ds_find_set_square(e.v2); + GUDHI_CHECK(a != b, std::logic_error("Bug in Gudhi")); + // This is more robust in case the input contains inf? I used to set the filtration of 0 to inf. + if (b == 0 || (a != 0 && input(a) < input(b))) std::swap(a, b); + ds_parent_square(b) = a; + if constexpr (output_index) + out(e.f.out(), b); + else + out(e.f.out(), input(b)); + } + } +}; +// Ideas for improvement: +// * for large hard (many intervals) inputs, primal/dual dominate the running time because of the random reads in +// find_set and input(a/b). The input load would be cheaper if we stored it with parents, but then find_set would +// be slower. +// * to increase memory locality, maybe pairing, which is currently arbitrary, could use some heuristic to favor +// some pairs over others. +// * try to loosen tight dependency chains, load values several instructions before they are needed. Performing 2 +// find_set in lock step surprisingly doesn't help. +// * To handle very big instances, we could remove data_v_ and recompute it on demand as the min of the inputs of i, +// i+1, i+dy and i+dy+1. On hard instances, it wouldn't save that much memory (and it is a bit slower). On easy +// instances, if we also remove the calls to reserve(), the saving is less negligible, but we still have ds_parent_*_ +// that take about as much space as the input. We could, on a subarray, fill a dense ds_parent, then reduce it and +// export only the critical vertices and boundary to some sparse datastructure, but it doesn't seem worth the trouble +// for now. + +/** + * @private + * Compute the persistence diagram of a function on a 2d cubical complex, defined as a lower-star filtration of the + * values at the top-dimensional cells. + * + * @tparam output_index If false, each argument of the out functors is a filtration value. If true, it is instead the + * index of this filtration value in the input. + * @tparam Filtration_value Must be comparable with `operator<`. + * @tparam Index This is used to index the elements of `input`, so it must be large enough to represent the size + * of `input`. + * @param[in] input Pointer to `n_rows*n_cols` filtration values for the square cells. Note that the values are assumed + * to be stored in C order, unlike `Gudhi::cubical_complex::Bitmap_cubical_complex` (you can exchange `n_rows` and + * `n_cols` for compatibility). + * @param[in] n_rows number of rows of `input`. + * @param[in] n_cols number of columns of `input`. + * @param[out] out0 For each interval (b, d) in the persistence diagram of dimension 0, the function calls `out0(b, d)`. + * @param[out] out1 Same as `out0` for persistence in dimension 1. + * @returns The global minimum, which is not paired and is thus the birth of an infinite persistence interval of + * dimension 0. + */ +template +auto persistence_on_rectangle_from_top_cells(Filtration_value const* input, Index n_rows, Index n_cols, + Out0&&out0, Out1&&out1){ +#ifdef GUDHI_DETAILED_TIMES + Gudhi::Clock clock; +#endif + GUDHI_CHECK(n_rows >= 2 && n_cols >= 2, + std::domain_error("The complex must truly be 2d, i.e. at least 2 rows and 2 columns")); + Persistence_on_rectangle X; + X.init(input, n_rows, n_cols); +#ifdef GUDHI_DETAILED_TIMES + std::clog << "init: " << clock; clock.begin(); +#endif + X.fill_and_pair(); +#ifdef GUDHI_DETAILED_TIMES + std::clog << "fill and pair: " << clock; clock.begin(); +#endif + X.sort_edges(); +#ifdef GUDHI_DETAILED_TIMES + std::clog << "sort: " << clock; clock.begin(); +#endif + X.primal(out0); +#ifdef GUDHI_DETAILED_TIMES + std::clog << "primal pass: " << clock; clock.begin(); +#endif + X.dual(out1); +#ifdef GUDHI_DETAILED_TIMES + std::clog << "dual pass: " << clock; +#endif + return X.global_min; +} +} // namespace Gudhi::cubical_complex + +#endif // PERSISTENCE_ON_RECTANGLE_H diff --git a/multipers/gudhi/gudhi/Persistent_cohomology.h b/multipers/gudhi/gudhi/Persistent_cohomology.h index b005be5f..1055c091 100644 --- a/multipers/gudhi/gudhi/Persistent_cohomology.h +++ b/multipers/gudhi/gudhi/Persistent_cohomology.h @@ -40,7 +40,7 @@ namespace persistent_cohomology { /** \brief Computes the persistent cohomology of a filtered complex. * * \ingroup persistent_cohomology - * + * * The computation is implemented with a Compressed Annotation Matrix * (CAM)\cite DBLP:conf/esa/BoissonnatDM13, * and is adapted to the computation of Multi-Field Persistent Homology (MF) @@ -165,8 +165,10 @@ class Persistent_cohomology { * * Assumes that the filtration provided by the simplicial complex is * valid. Undefined behavior otherwise. */ - void compute_persistent_cohomology(Filtration_value min_interval_length = 0, - const bool is_simplicial_and_starting_from_dim_0 = true) { + void compute_persistent_cohomology(Filtration_value min_interval_length = 0) { + if (dim_max_ <= 0) + return; // --------->> + interval_length_policy.set_length(min_interval_length); Simplex_key idx_fil = -1; std::vector vertices; // so we can check the connected components at the end @@ -175,20 +177,16 @@ class Persistent_cohomology { cpx_->assign_key(sh, ++idx_fil); dsets_.make_set(cpx_->key(sh)); int dim_simplex = cpx_->dimension(sh); - if (is_simplicial_and_starting_from_dim_0){ - switch (dim_simplex) { - case 0: - vertices.push_back(idx_fil); - break; - case 1: - update_cohomology_groups_edge(sh); - break; - default: - update_cohomology_groups(sh, dim_simplex); - break; - } - } else { - update_cohomology_groups(sh, dim_simplex); + switch (dim_simplex) { + case 0: + vertices.push_back(idx_fil); + break; + case 1: + update_cohomology_groups_edge(sh); + break; + default: + update_cohomology_groups(sh, dim_simplex); + break; } } // Compute infinite intervals of dimension 0 @@ -210,6 +208,32 @@ class Persistent_cohomology { } } + /** + * @private + * Temporary patch to make the computation of more general FilteredComplex possible + * Will be removed when a better solution was agreed on. + */ + void compute_persistent_cohomology_without_optimizations(Filtration_value min_interval_length = 0) { + if (dim_max_ <= 0) + return; // --------->> + + interval_length_policy.set_length(min_interval_length); + Simplex_key idx_fil = -1; + // Compute all finite intervals + for (auto sh : cpx_->filtration_simplex_range()) { + cpx_->assign_key(sh, ++idx_fil); + dsets_.make_set(cpx_->key(sh)); + int dim_simplex = cpx_->dimension(sh); + update_cohomology_groups(sh, dim_simplex); + } + + // Compute infinite interval of dimension > 0 + for (auto cocycle : transverse_idx_) { + persistent_pairs_.emplace_back( + cpx_->simplex(cocycle.first), cpx_->null_simplex(), cocycle.second.characteristics_); + } + } + private: /** \brief Update the cohomology groups under the insertion of an edge. * diff --git a/multipers/gudhi/gudhi/Projective_cover_kernel.h b/multipers/gudhi/gudhi/Projective_cover_kernel.h new file mode 100644 index 00000000..c490b8b6 --- /dev/null +++ b/multipers/gudhi/gudhi/Projective_cover_kernel.h @@ -0,0 +1,375 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - 2025/04 Hannah Schreiber: Reorganization + documentation. + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Projective_cover_kernel.h + * @author David Loiseaux + * @brief Contains the @ref Gudhi::multi_persistence::Projective_cover_kernel class. + */ + +#ifndef MP_PROJECTIVE_COVER_KERNEL_H_INCLUDED +#define MP_PROJECTIVE_COVER_KERNEL_H_INCLUDED + +#include +#include //std::move +#include +#include + +#include +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Projective_cover_kernel Projective_cover_kernel.h gudhi/Projective_cover_kernel.h + * @ingroup multi_persistence + * + * @brief TODO (what it is + mention that it only works for 2 parameters) + * + * @tparam MultiFiltrationValue Filtration value class respecting the @ref MultiFiltrationValue concept. + * @tparam columnType Column type to use for the matrix used internally. Default value: + * @ref Gudhi::persistence_matrix::Column_types::NAIVE_VECTOR "NAIVE_VECTOR". + */ +template +class Projective_cover_kernel +{ + private: + /** + * @brief Options for matrix type. + */ + struct Matrix_options : Gudhi::persistence_matrix::Default_options { + using Index = std::uint32_t; + }; + + public: + using Filtration_value = MultiFiltrationValue; /**< Filtration value type. */ + using Complex = Multi_parameter_filtered_complex; /**< Complex data type. */ + using Index = typename Complex::Index; /**< Index type. */ + using Dimension = typename Complex::Dimension; /**< Dimension type. */ + using Filtration_value_container = typename Complex::Filtration_value_container; /**< Filt. value container type. */ + using Boundary_container = typename Complex::Boundary_container; /**< Boundary container type. */ + using Dimension_container = typename Complex::Dimension_container; /**< Dimension container type. */ + using Matrix = Gudhi::persistence_matrix::Matrix; /**< Matrix type. */ + + // TODO: this only works for 2 parameter modules. Optimize w.r.t. this. + /** + * @brief Constructor. + * + * @param complex Complex containing the boundaries, dimensions and 2-parameter filtration values necessary, + * ordered by dimension and then co-lexicographically with respect to the filtration values. + * @param dim Dimension for which to compute the kernel. + */ + Projective_cover_kernel(const Complex &complex, Dimension dim) + { + using namespace Gudhi::multi_filtration; + + if (complex.get_number_of_parameters() != 2) throw std::invalid_argument("Only available for 2-parameter modules."); + if (!complex.is_ordered_by_dimension()) throw std::invalid_argument("Complex has to be ordered by dimension."); + + const auto &boundaries = complex.get_boundaries(); + const auto &filtValues = complex.get_filtration_values(); + const auto &dimensions = complex.get_dimensions(); + + GUDHI_CHECK_code( + Index i = 0; + for (; i < complex.get_number_of_cycle_generators() - 1; ++i) { + GUDHI_CHECK(filtValues[i].num_generators() == 1, std::invalid_argument("Only available for 1-critical modules.")); + GUDHI_CHECK(dimensions[i] <= dimensions[i + 1], std::invalid_argument("Cells have to be ordered by dimension.")); + if (dimensions[i] == dimensions[i + 1]) + GUDHI_CHECK(is_less_or_equal_than_lexicographically(filtValues[i], filtValues[i + 1]), + std::invalid_argument("Cells with same dimension have to be ordered co-lexicographically.")); + } + GUDHI_CHECK(filtValues[i].num_generators() == 1, std::invalid_argument("Only available for 1-critical modules.")); + ) + + Index startDim1, startDim2, end; + _get_complex_indices(complex, dim, startDim1, startDim2, end); + + Index nberDim = startDim2 - startDim1; + Index nberDim1 = end - startDim2; + Index nberGen = nberDim + nberDim1; + Index shift = startDim1; + + Matrix M(nberGen); + Matrix N(nberGen); // slave + std::vector isReduced(nberGen); + // TODO : pivot caches are small : maybe use a flat container instead ? + std::vector> pivotCache(nberGen); // this[pivot] = cols of given pivot + + auto get_pivot = [&M, shift](Index i) { return _get_pivot(M, i, shift); }; + + _initialize_matrices(startDim1, end, boundaries, M, N); + _initialize_containers(nberDim, nberGen, shift, filtValues, dimensions, get_pivot, pivotCache, isReduced); + SmallQueue lexicoIt = _initialize_lex_queue(nberDim, nberGen, shift, filtValues, pivotCache, get_pivot); + + while (!lexicoIt.empty()) { + Filtration_value gridValue = lexicoIt.pop(); + for (int i : lexicoIt.get_current_cols()) { + while (_reduce_column(complex, i, isReduced, pivotCache, M, N, gridValue, get_pivot)); + _update_after_new_pivot(i, lexicoIt, pivotCache, filtValues, gridValue, get_pivot); + } + } + } + + /** + * @brief Returns the kernel generators. (? TODO) + */ + Boundary_container build_generators() + { + Index start = 0; + while (dimensions_[start] == dimensions_[0]) ++start; + return Boundary_container(boundaries_.begin() + start, boundaries_.end()); + } + + /** + * @brief Returns a complex representing the projective cover kernel. + */ + Complex create_complex() { return Complex(boundaries_, dimensions_, filtrationValues_); } + + private: + /** + * @brief Lexicographically ordered queue. Each filtration value is represented once and contains the list of indices + * of the columns with this filtration value. + */ + struct SmallQueue { + SmallQueue() = default; + + struct MFWrapper { + MFWrapper(const Filtration_value &g) : g(g) {}; + + MFWrapper(const Filtration_value &g, int col) : g(g) { someCols.insert(col); } + + MFWrapper(const Filtration_value &g, std::initializer_list cols) : g(g), someCols(cols.begin(), cols.end()) + {} + + void insert(int col) const { someCols.insert(col); } + + bool operator<(const MFWrapper &other) const { return is_strict_less_than_lexicographically(g, other.g); } + + public: + Filtration_value g; + mutable std::set someCols; + }; + + void insert(const Filtration_value &g, int col) + { + auto it = queue.find(g); + if (it != queue.end()) { + it->insert(col); + } else { + queue.emplace(g, col); + } + }; + + void insert(const Filtration_value &g, const std::initializer_list &cols) + { + auto it = queue.find(g); + if (it != queue.end()) { + for (int c : cols) it->insert(c); + } else { + queue.emplace(g, cols); + } + }; + + [[nodiscard]] bool empty() const { return queue.empty(); } + + Filtration_value pop() + { + if (queue.empty()) throw std::runtime_error("Queue is empty"); + + auto out = std::move(*queue.begin()); + queue.erase(queue.begin()); + std::swap(lastCols, out.someCols); + return out.g; + } + + const auto &get_current_cols() const { return lastCols; } + + private: + std::set queue; + std::set lastCols; + }; + + Boundary_container boundaries_; /** Boundary container. */ + Dimension_container dimensions_; /** Dimension container. */ + Filtration_value_container filtrationValues_; /** Filtration value container. */ + + static void _get_complex_indices(const Complex &complex, + Dimension dim, + Index &startDim1, + Index &startDim2, + Index &end) + { + const auto &dims = complex.get_dimensions(); + startDim1 = 0; + startDim2 = 0; + end = 0; + + Index i = 0; + auto size = complex.get_number_of_cycle_generators(); + for (; i < size && dims[i] < dim; ++i); + if (i == size) throw std::invalid_argument("Given dimension has no generators."); + startDim1 = i; + for (; i < size && dims[i] == dim; ++i); + if (i == size || dims[i] > dim + 1) throw std::invalid_argument("Given dimension has no generators."); + startDim2 = i; + for (; i < size && dims[i] == dim + 1; ++i); + end = i; + } + + static int _get_pivot(Matrix &M, Index i, Index shift) + { + const auto &col = M.get_column(i); + return col.size() > 0 ? (*col.rbegin()).get_row_index() - shift : -1; + } + + static void _initialize_matrices(Index start, Index end, const Boundary_container &boundaries, Matrix &M, Matrix &N) + { + for (Index i = start; i < end; i++) { + M.insert_boundary(boundaries[i]); + N.insert_boundary({i - start}); + } + } + + template + static SmallQueue _initialize_lex_queue(Index nberDim, + Index nberGen, + Index shift, + const Filtration_value_container &filtValues, + const std::vector> &pivotCache, + F &&get_pivot) + { + SmallQueue lexicoIt; + for (Index i = nberDim; i < nberGen; ++i) { + int pivot = std::forward(get_pivot)(i); + if (pivot >= 0) { + lexicoIt.insert(filtValues[i + shift], i); + auto it = pivotCache[pivot].find(i); + GUDHI_CHECK(it != pivotCache[pivot].end(), std::runtime_error("Column not registered in pivot cache.")); + it++; + for (; it != pivotCache[pivot].end(); ++it) { + int colIdx = *it; + GUDHI_CHECK(static_cast(colIdx) > i, std::runtime_error("Column not registered in the right order.")); + auto prev = filtValues[colIdx + shift]; + prev.push_to_least_common_upper_bound(filtValues[i + shift]); + lexicoIt.insert(std::move(prev), colIdx); + } + } + } + return lexicoIt; + } + + template + static void _update_after_new_pivot(Index i, + SmallQueue &lexicoIt, + std::vector> &pivotCache, + const Filtration_value_container &filtValues, + const Filtration_value &gridValue, + F &&get_pivot) + { + int pivot = std::forward(get_pivot)(i); + + if (pivot < 0) return; + + auto [it, wasThere] = pivotCache[pivot].insert(i); + it++; + for (; it != pivotCache[pivot].end(); ++it) { + int colIdx = *it; + GUDHI_CHECK(static_cast(colIdx) > i, std::runtime_error("(update) Column not registered in the right order.")); + auto prev = filtValues[colIdx]; + if (!(prev >= filtValues[i])) { + prev.push_to_least_common_upper_bound(filtValues[i]); + if (is_strict_less_than_lexicographically(gridValue, prev)) { + lexicoIt.insert(prev, colIdx); + } + } + } + } + + template + void _initialize_containers(Index nberDim, + Index nberGen, + Index shift, + const Filtration_value_container &filtValues, + const Dimension_container &dimensions, + F &&get_pivot, + std::vector> &pivotCache, + std::vector &isReduced) + { + Index size = (nberGen - nberDim) * 2; + boundaries_.reserve(size); + filtrationValues_.reserve(size); + dimensions_.reserve(size); + + for (Index i = nberDim; i < nberGen; ++i) { + boundaries_.emplace_back(); + filtrationValues_.push_back(filtValues[i + shift]); + dimensions_.push_back(dimensions[i + shift]); + int pivot = std::forward(get_pivot)(i); + if (pivot < 0) { + isReduced[i] = true; + } else { + auto ¤tCache = pivotCache[pivot]; + currentCache.emplace_hint(currentCache.cend(), i); // j is increasing + } + } + } + + template + bool _reduce_column(const Complex &complex, + Index i, + std::vector &isReduced, + std::vector> &pivotCache, + Matrix &M, + Matrix &N, + const Filtration_value &gridValue, + F &&get_pivot) + { + int pivot = std::forward(get_pivot)(i); + if (pivot < 0) { + if (!isReduced[i]) { + const auto &col = N.get_column(i); + boundaries_.emplace_back(col.begin(), col.end()); + filtrationValues_.emplace_back(gridValue); + dimensions_.emplace_back(complex.get_dimensions()[i] + 1); + isReduced[i] = true; + } + return false; + } + // WARN : we lazy update variables linked with col i... + if (pivotCache[pivot].size() == 0) { + return false; + } + const auto &filtValues = complex.get_filtration_values(); + for (Index k : pivotCache[pivot]) { + if (k >= i) { // cannot reduce more here. this is a (local) pivot. + return false; + } + if (filtValues[k] <= gridValue) { + M.add_to(k, i); + N.add_to(k, i); + pivotCache[pivot].erase(i); + // WARN : we update the pivot cache after the update loop + GUDHI_CHECK(std::forward(get_pivot)(i) < pivot, std::runtime_error("Column addition failed.")); + return true; // pivot has changed + } + } + return false; // for loop exhausted (i may not be there because of lazy) + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_PROJECTIVE_COVER_KERNEL_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Simplex_tree.h b/multipers/gudhi/gudhi/Simplex_tree.h index 500acc74..28d5ee5a 100644 --- a/multipers/gudhi/gudhi/Simplex_tree.h +++ b/multipers/gudhi/gudhi/Simplex_tree.h @@ -12,6 +12,10 @@ * - 2023/08 Hannah Schreiber (& Clément Maria): Add possibility of stable simplex handles. * - 2024/08 Hannah Schreiber: Generalization of the notion of filtration values. * - 2024/08 Hannah Schreiber: Addition of customizable copy constructor. + * - 2024/08 Marc Glisse: Allow storing custom data in simplices. + * - 2024/10 Hannah Schreiber: Const version of the Simplex_tree + * - 2025/02 Hannah Schreiber (& David Loiseaux): Insertion strategies for `insert_simplex_and_subfaces` + * - 2025/03 Hannah Schreiber (& David Loiseaux): Add number_of_parameters_ member * - YYYY/MM Author: Description of the modification */ @@ -23,7 +27,7 @@ #include #include #include -#include // for Gudhi::simplex_tree::de/serialize_trivial +#include // for de/serialize + empty_filtration_value #include #include @@ -42,20 +46,21 @@ #include #include -#include #ifdef GUDHI_USE_TBB #include #endif +#include +#include // std::uint8_t #include // for std::move #include #include // for greater<> #include #include // Inf #include -#include // for std::max -#include // for std::distance +#include // for std::max +#include // for std::distance #include // for std::conditional #include #include // for std::prev @@ -78,7 +83,7 @@ namespace Gudhi { * Details may be found in \cite Cohen-Steiner2009 and section 2.2 in \cite Carriere16. * */ -enum class Extended_simplex_type { UP, DOWN, EXTRA }; +enum class Extended_simplex_type {UP, DOWN, EXTRA}; /** * \class Simplex_tree Simplex_tree.h gudhi/Simplex_tree.h @@ -93,20 +98,24 @@ enum class Extended_simplex_type { UP, DOWN, EXTRA }; * */ -template +template class Simplex_tree { public: typedef SimplexTreeOptions Options; typedef typename Options::Indexing_tag Indexing_tag; /** \brief Type for the value of the filtration function. * - * Must be comparable with <. */ + * Should implement the @ref FiltrationValue concept. */ typedef typename Options::Filtration_value Filtration_value; /** \brief Key associated to each simplex. * * Must be an integer type. */ typedef typename Options::Simplex_key Simplex_key; - /** \brief Extra data stored in each simplex. */ + /** \brief Extra data stored in each simplex. + * + * When extra data type is defined by the user, the extra data gets a + * default-initialization + * behaviour, which may mean an indeterminate value. */ typedef typename Get_simplex_data_type::type Simplex_data; /** \brief Type for the vertex handle. * @@ -120,30 +129,26 @@ class Simplex_tree { // flat_set (with our own comparator) where we can control the layout of the struct (put Vertex_handle and // Simplex_key next to each other). typedef typename boost::container::flat_map flat_map; - // Dictionary::iterator remain valid under insertions and deletions, - // necessary e.g. when computing oscillating rips zigzag filtrations. + //Dictionary::iterator remain valid under insertions and deletions, + //necessary e.g. when computing oscillating rips zigzag filtrations. typedef typename boost::container::map map; - typedef typename std::conditional::type Dictionary; + typedef typename std::conditional::type Dictionary; /** \brief Set of nodes sharing a same parent in the simplex tree. */ typedef Simplex_tree_siblings Siblings; struct Key_simplex_base_real { Key_simplex_base_real() : key_(-1) {} - Key_simplex_base_real(Simplex_key k) : key_(k) {} - void assign_key(Simplex_key k) { key_ = k; } - Simplex_key key() const { return key_; } - private: Simplex_key key_; }; - struct Key_simplex_base_dummy { Key_simplex_base_dummy() {} - // Undefined so it will not link void assign_key(Simplex_key); Simplex_key key() const; @@ -152,27 +157,30 @@ class Simplex_tree { struct Extended_filtration_data { Filtration_value minval; Filtration_value maxval; - Extended_filtration_data() {} - Extended_filtration_data(const Filtration_value& vmin, const Filtration_value& vmax) : minval(vmin), maxval(vmax) {} + Extended_filtration_data(const Filtration_value& vmin, const Filtration_value& vmax) + : minval(vmin), maxval(vmax) + {} }; - typedef typename std::conditional::type Key_simplex_base; struct Filtration_simplex_base_real { - Filtration_simplex_base_real() : filt_(0) {} - + Filtration_simplex_base_real() : filt_() {} Filtration_simplex_base_real(Filtration_value f) : filt_(f) {} - void assign_filtration(const Filtration_value& f) { filt_ = f; } - const Filtration_value& filtration() const { return filt_; } - Filtration_value& filtration() { return filt_; } static const Filtration_value& get_infinity() { return inf_; } + static Filtration_value get_minus_infinity() { + if constexpr (std::numeric_limits::has_infinity) { + return -inf_; + } else { + return std::numeric_limits::lowest(); + } + } private: Filtration_value filt_; @@ -184,25 +192,19 @@ class Simplex_tree { struct Filtration_simplex_base_dummy { Filtration_simplex_base_dummy() {} - Filtration_simplex_base_dummy(Filtration_value GUDHI_CHECK_code(f)) { - GUDHI_CHECK(f == Filtration_value(), - "filtration value specified in the constructor for a complex that does not store them"); + GUDHI_CHECK(f == null_, "filtration value specified in the constructor for a complex that does not store them"); } - void assign_filtration(const Filtration_value& GUDHI_CHECK_code(f)) { - GUDHI_CHECK(f == Filtration_value(), "filtration value assigned for a complex that does not store them"); + GUDHI_CHECK(f == null_, "filtration value assigned for a complex that does not store them"); } - const Filtration_value& filtration() const { return null_; } private: - static constexpr const Filtration_value null_ = Filtration_value(); + inline static const Filtration_value null_{Gudhi::simplex_tree::empty_filtration_value}; }; - - typedef typename std::conditional::type Filtration_simplex_base; + typedef typename std::conditional::type Filtration_simplex_base; public: /** \brief Handle type to a simplex contained in the simplicial complex represented @@ -211,14 +213,17 @@ class Simplex_tree { * They are essentially pointers into internal vectors, and any insertion or removal * of a simplex may invalidate any other Simplex_handle in the complex, * unless Options::stable_simplex_handles == true. */ - typedef typename Dictionary::iterator Simplex_handle; + typedef typename Dictionary::const_iterator Simplex_handle; private: typedef typename Dictionary::iterator Dictionary_it; + typedef typename Dictionary::const_iterator Dictionary_const_it; typedef typename Dictionary_it::value_type Dit_value_t; struct return_first { - Vertex_handle operator()(const Dit_value_t& p_sh) const { return p_sh.first; } + Vertex_handle operator()(const Dit_value_t& p_sh) const { + return p_sh.first; + } }; private: @@ -233,14 +238,13 @@ class Simplex_tree { using Optimized_star_simplex_range = boost::iterator_range; class Fast_cofaces_predicate { - Simplex_tree* st_; + Simplex_tree const* st_; int codim_; int dim_; - public: - Fast_cofaces_predicate(Simplex_tree* st, int codim, int dim) : st_(st), codim_(codim), dim_(codim + dim) {} - - bool operator()(const Simplex_handle iter) const { + Fast_cofaces_predicate(Simplex_tree const* st, int codim, int dim) + : st_(st), codim_(codim), dim_(codim + dim) {} + bool operator()( const Simplex_handle iter ) const { if (codim_ == 0) // Always true for a star return true; @@ -251,13 +255,13 @@ class Simplex_tree { // WARNING: this is safe only because boost::filtered_range is containing a copy of begin and end iterator. // This would not be safe if it was containing a pointer to a range (maybe the case for std::views) - using Optimized_cofaces_simplex_filtered_range = - boost::filtered_range; + using Optimized_cofaces_simplex_filtered_range = boost::filtered_range; + /** The largest dimension supported for simplex trees. * 40 seems a conservative bound for now, as 2^41 simplices would not fit on the biggest hard-drive. */ static constexpr int max_dimension() { return 40; } - public: /** \name Range and iterator types * @@ -270,7 +274,7 @@ class Simplex_tree { /** \brief Iterator over the vertices of the simplicial complex. * * 'value_type' is Vertex_handle. */ - typedef boost::transform_iterator Complex_vertex_iterator; + typedef boost::transform_iterator Complex_vertex_iterator; /** \brief Range over the vertices of the simplicial complex. */ typedef boost::iterator_range Complex_vertex_range; /** \brief Iterator over the vertices of a simplex. @@ -298,8 +302,7 @@ class Simplex_tree { /** \brief Iterator over the simplices of the boundary of a simplex and their opposite vertices. * * 'value_type' is std::pair. */ - typedef Simplex_tree_boundary_opposite_vertex_simplex_iterator - Boundary_opposite_vertex_simplex_iterator; + typedef Simplex_tree_boundary_opposite_vertex_simplex_iterator Boundary_opposite_vertex_simplex_iterator; /** \brief Range over the simplices of the boundary of a simplex and their opposite vertices. */ typedef boost::iterator_range Boundary_opposite_vertex_simplex_range; /** \brief Iterator over the simplices of the simplicial complex. @@ -316,6 +319,12 @@ class Simplex_tree { /** \brief Range over the simplices of the skeleton of the simplicial complex, for a given * dimension. */ typedef boost::iterator_range Skeleton_simplex_range; + /** \brief Iterator over the simplices of the simplicial complex that match the dimension specified by the parameter. + * + * 'value_type' is Simplex_handle. */ + typedef Simplex_tree_dimension_simplex_iterator Dimension_simplex_iterator; + /** \brief Range over the simplices of the simplicial complex that match a given dimension. */ + typedef boost::iterator_range Dimension_simplex_range; /** \brief Range over the simplices of the simplicial complex, ordered by the filtration. */ typedef std::vector Filtration_simplex_range; /** \brief Iterator over the simplices of the simplicial complex, ordered by the filtration. @@ -329,7 +338,7 @@ class Simplex_tree { /** \brief Returns a range over the vertices of the simplicial complex. * The order is increasing according to < on Vertex_handles.*/ - Complex_vertex_range complex_vertex_range() { + Complex_vertex_range complex_vertex_range() const { return Complex_vertex_range(boost::make_transform_iterator(root_.members_.begin(), return_first()), boost::make_transform_iterator(root_.members_.end(), return_first())); } @@ -339,8 +348,9 @@ class Simplex_tree { * In the Simplex_tree, the tree is traverse in a depth-first fashion. * Consequently, simplices are ordered according to lexicographic order on the list of * Vertex_handles of a simplex, read in increasing < order for Vertex_handles. */ - Complex_simplex_range complex_simplex_range() { - return Complex_simplex_range(Complex_simplex_iterator(this), Complex_simplex_iterator()); + Complex_simplex_range complex_simplex_range() const { + return Complex_simplex_range(Complex_simplex_iterator(this), + Complex_simplex_iterator()); } /** \brief Returns a range over the simplices of the dim-skeleton of the simplicial complex. @@ -352,8 +362,21 @@ class Simplex_tree { * * The simplices are ordered according to lexicographic order on the list of * Vertex_handles of a simplex, read in increasing < order for Vertex_handles. */ - Skeleton_simplex_range skeleton_simplex_range(int dim) { - return Skeleton_simplex_range(Skeleton_simplex_iterator(this, dim), Skeleton_simplex_iterator()); + Skeleton_simplex_range skeleton_simplex_range(int dim) const { + return Skeleton_simplex_range(Skeleton_simplex_iterator(this, dim), + Skeleton_simplex_iterator()); + } + + /** \brief Returns a range over the simplices of the simplicial complex that match the dimension specified by the + * parameter. + * + * @param[in] dim The exact dimension of the simplices. + * + * The simplices are ordered according to lexicographic order on the list of + * Vertex_handles of a simplex, read in increasing < order for Vertex_handles. */ + Dimension_simplex_range dimension_simplex_range(int dim) const { + return Dimension_simplex_range(Dimension_simplex_iterator(this, dim), + Dimension_simplex_iterator()); } /** \brief Returns a range over the simplices of the simplicial complex, @@ -370,8 +393,11 @@ class Simplex_tree { * * The filtration must be valid. If the filtration has not been initialized yet, the * method initializes it (i.e. order the simplices). If the complex has changed since the last time the filtration - * was initialized, please call `clear_filtration()` or `initialize_filtration()` to recompute it. */ - Filtration_simplex_range const& filtration_simplex_range(Indexing_tag = Indexing_tag()) { + * was initialized, please call `clear_filtration()` or `initialize_filtration()` to recompute it. + * + * @note Not thread safe + */ + Filtration_simplex_range const& filtration_simplex_range(Indexing_tag = Indexing_tag()) const { maybe_initialize_filtration(); return filtration_vect_; } @@ -384,7 +410,8 @@ class Simplex_tree { */ Simplex_vertex_range simplex_vertex_range(Simplex_handle sh) const { GUDHI_CHECK(sh != null_simplex(), "empty simplex"); - return Simplex_vertex_range(Simplex_vertex_iterator(this, sh), Simplex_vertex_iterator(this)); + return Simplex_vertex_range(Simplex_vertex_iterator(this, sh), + Simplex_vertex_iterator(this)); } /** \brief Returns a range over the simplices of the boundary of a simplex. @@ -401,9 +428,10 @@ class Simplex_tree { * of the simplex. * * @param[in] sh Simplex for which the boundary is computed. */ - template - Boundary_simplex_range boundary_simplex_range(SimplexHandle sh) { - return Boundary_simplex_range(Boundary_simplex_iterator(this, sh), Boundary_simplex_iterator(this)); + template + Boundary_simplex_range boundary_simplex_range(SimplexHandle sh) const { + return Boundary_simplex_range(Boundary_simplex_iterator(this, sh), + Boundary_simplex_iterator(this)); } /** \brief Given a simplex, returns a range over the simplices of its boundary and their opposite vertices. @@ -417,8 +445,8 @@ class Simplex_tree { * * @param[in] sh Simplex for which the boundary is computed. */ - template - Boundary_opposite_vertex_simplex_range boundary_opposite_vertex_simplex_range(SimplexHandle sh) { + template + Boundary_opposite_vertex_simplex_range boundary_opposite_vertex_simplex_range(SimplexHandle sh) const { return Boundary_opposite_vertex_simplex_range(Boundary_opposite_vertex_simplex_iterator(this, sh), Boundary_opposite_vertex_simplex_iterator(this)); } @@ -428,9 +456,13 @@ class Simplex_tree { * @{ */ /** \brief Constructs an empty simplex tree. */ - Simplex_tree() : null_vertex_(-1), root_(nullptr, null_vertex_), filtration_vect_(), dimension_(-1) { - if constexpr (Options::is_multi_parameter) number_of_parameters_ = 2; - } + Simplex_tree() + : null_vertex_(-1), + root_(nullptr, null_vertex_), + filtration_vect_(), + dimension_(-1), + dimension_to_be_lowered_(false), + number_of_parameters_(1) {} /** * @brief Construct the simplex tree as the copy of a given simplex tree with eventually different template @@ -447,7 +479,7 @@ class Simplex_tree { * @param translate_filtration_value Method taking an OtherSimplexTreeOptions::Filtration_value from the source tree * as input and returning the corresponding Options::Filtration_value in the new tree. */ - template + template Simplex_tree(const Simplex_tree& complex_source, F&& translate_filtration_value) { #ifdef DEBUG_TRACES std::clog << "Simplex_tree custom copy constructor" << std::endl; @@ -455,7 +487,9 @@ class Simplex_tree { copy_from(complex_source, translate_filtration_value); } - /** \brief User-defined copy constructor reproduces the whole tree structure. */ + /** \brief User-defined copy constructor reproduces the whole tree structure including extra data (@ref Simplex_data) + * stored in the simplices. + */ Simplex_tree(const Simplex_tree& complex_source) { #ifdef DEBUG_TRACES std::clog << "Simplex_tree copy constructor" << std::endl; @@ -463,25 +497,26 @@ class Simplex_tree { copy_from(complex_source); } - /** \brief User-defined move constructor relocates the whole tree structure. + /** \brief User-defined move constructor relocates the whole tree structure including extra data (@ref Simplex_data) + * stored in the simplices. * \exception std::invalid_argument In debug mode, if the complex_source is invalid. */ - Simplex_tree(Simplex_tree&& complex_source) : number_of_parameters_(std::move(complex_source.number_of_parameters_)) { + Simplex_tree(Simplex_tree && complex_source) { #ifdef DEBUG_TRACES std::clog << "Simplex_tree move constructor" << std::endl; #endif // DEBUG_TRACES move_from(complex_source); - - // just need to set dimension_ on source to make it available again - // (filtration_vect_ and members are already set from the move) - complex_source.dimension_ = -1; } /** \brief Destructor; deallocates the whole tree structure. */ - ~Simplex_tree() { root_members_recursive_deletion(); } + ~Simplex_tree() { + root_members_recursive_deletion(); + } - /** \brief User-defined copy assignment reproduces the whole tree structure. */ - Simplex_tree& operator=(const Simplex_tree& complex_source) { + /** \brief User-defined copy assignment reproduces the whole tree structure including extra data (@ref Simplex_data) + * stored in the simplices. + */ + Simplex_tree& operator= (const Simplex_tree& complex_source) { #ifdef DEBUG_TRACES std::clog << "Simplex_tree copy assignment" << std::endl; #endif // DEBUG_TRACES @@ -495,7 +530,8 @@ class Simplex_tree { return *this; } - /** \brief User-defined move assignment relocates the whole tree structure. + /** \brief User-defined move assignment relocates the whole tree structure including extra data (@ref Simplex_data) + * stored in the simplices. * \exception std::invalid_argument In debug mode, if the complex_source is invalid. */ Simplex_tree& operator=(Simplex_tree&& complex_source) { @@ -511,7 +547,6 @@ class Simplex_tree { } return *this; } - /** @} */ // end constructor/destructor private: @@ -519,7 +554,9 @@ class Simplex_tree { void copy_from(const Simplex_tree& complex_source) { null_vertex_ = complex_source.null_vertex_; filtration_vect_.clear(); + number_of_parameters_ = complex_source.number_of_parameters_; dimension_ = complex_source.dimension_; + dimension_to_be_lowered_ = complex_source.dimension_to_be_lowered_; auto root_source = complex_source.root_; // root members copy @@ -529,22 +566,37 @@ class Simplex_tree { for (auto& map_el : root_.members()) { map_el.second.assign_children(&root_); } - rec_copy( + // Specific for optional data + if constexpr (!std::is_same_v) { + auto dst_iter = root_.members().begin(); + auto src_iter = root_source.members().begin(); + + while(dst_iter != root_.members().end() || src_iter != root_source.members().end()) { + dst_iter->second.data() = src_iter->second.data(); + dst_iter++; + src_iter++; + } + // Check in debug mode members data were copied + assert(dst_iter == root_.members().end()); + assert(src_iter == root_source.members().end()); + } + rec_copy( &root_, &root_source, [](const Filtration_value& fil) -> const Filtration_value& { return fil; }); - if constexpr (Options::is_multi_parameter) number_of_parameters_ = complex_source.number_of_parameters_; } // Copy from complex_source to "this" - template + template void copy_from(const Simplex_tree& complex_source, F&& translate_filtration_value) { null_vertex_ = complex_source.null_vertex_; filtration_vect_.clear(); + number_of_parameters_ = complex_source.number_of_parameters_; dimension_ = complex_source.dimension_; + dimension_to_be_lowered_ = complex_source.dimension_to_be_lowered_; auto root_source = complex_source.root_; // root members copy if constexpr (!Options::stable_simplex_handles) root_.members().reserve(root_source.size()); - for (auto& p : root_source.members()) { + for (auto& p : root_source.members()){ if constexpr (Options::store_key && OtherSimplexTreeOptions::store_key) { auto it = root_.members().try_emplace( root_.members().end(), p.first, &root_, translate_filtration_value(p.second.filtration()), p.second.key()); @@ -554,34 +606,38 @@ class Simplex_tree { } } - rec_copy(&root_, &root_source, translate_filtration_value); - if constexpr (Options::is_multi_parameter) number_of_parameters_ = complex_source.number_of_parameters_; + rec_copy(&root_, &root_source, translate_filtration_value); } /** \brief depth first search, inserts simplices when reaching a leaf. */ - template - void rec_copy(Siblings* sib, OtherSiblings* sib_source, F&& translate_filtration_value) { + template + void rec_copy(Siblings *sib, OtherSiblings *sib_source, F&& translate_filtration_value) { auto sh_source = sib_source->members().begin(); for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh, ++sh_source) { update_simplex_tree_after_node_insertion(sh); if (has_children(sh_source)) { - Siblings* newsib = new Siblings(sib, sh_source->first); + Siblings * newsib = new Siblings(sib, sh_source->first); if constexpr (!Options::stable_simplex_handles) { newsib->members_.reserve(sh_source->second.children()->members().size()); } - for (auto& child : sh_source->second.children()->members()) { + for (auto & child : sh_source->second.children()->members()){ + Dictionary_it new_it{}; if constexpr (store_key && Options::store_key) { - newsib->members_.emplace_hint( + new_it = newsib->members_.emplace_hint( newsib->members_.end(), child.first, Node(newsib, translate_filtration_value(child.second.filtration()), child.second.key())); } else { - newsib->members_.emplace_hint(newsib->members_.end(), + new_it = newsib->members_.emplace_hint(newsib->members_.end(), child.first, Node(newsib, translate_filtration_value(child.second.filtration()))); } + // Specific for optional data + if constexpr (copy_simplex_data && !std::is_same_v) { + new_it->second.data() = child.second.data(); + } } - rec_copy(newsib, sh_source->second.children(), translate_filtration_value); + rec_copy(newsib, sh_source->second.children(), translate_filtration_value); sh->second.assign_children(newsib); } } @@ -592,7 +648,9 @@ class Simplex_tree { null_vertex_ = std::move(complex_source.null_vertex_); root_ = std::move(complex_source.root_); filtration_vect_ = std::move(complex_source.filtration_vect_); - dimension_ = complex_source.dimension_; + number_of_parameters_ = std::exchange(complex_source.number_of_parameters_, 1); + dimension_ = std::exchange(complex_source.dimension_, -1); + dimension_to_be_lowered_ = std::exchange(complex_source.dimension_to_be_lowered_, false); if constexpr (Options::link_nodes_by_label) { nodes_label_to_list_.swap(complex_source.nodes_label_to_list_); } @@ -622,7 +680,7 @@ class Simplex_tree { } // Recursive deletion - void rec_delete(Siblings* sib) { + void rec_delete(Siblings * sib) { for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { if (has_children(sh)) { rec_delete(sh->second.children()); @@ -632,45 +690,44 @@ class Simplex_tree { } public: - template - friend class Simplex_tree; + template friend class Simplex_tree; - /** \brief Checks if two simplex trees are equal. */ + /** \brief Checks if two simplex trees are equal. Any extra data (@ref Simplex_data) stored in the simplices are + * ignored in the comparison. + */ template - bool operator==(Simplex_tree& st2) { - if constexpr (Options::is_multi_parameter) { - if constexpr (!OtherSimplexTreeOptions::is_multi_parameter) { - return false; - } else { - if (st2.get_number_of_parameters() != this->get_number_of_parameters()) { - return false; - } - } - } + bool operator==(const Simplex_tree& st2) const { if ((null_vertex_ != st2.null_vertex_) || - (dimension_ != st2.dimension_ && !dimension_to_be_lowered_ && !st2.dimension_to_be_lowered_)) + (dimension_ != st2.dimension_ && !dimension_to_be_lowered_ && !st2.dimension_to_be_lowered_) || + (number_of_parameters_ != st2.number_of_parameters_)) return false; return rec_equal(&root_, &st2.root_); } /** \brief Checks if two simplex trees are different. */ - template - bool operator!=(Simplex_tree& st2) { + template + bool operator!=(const Simplex_tree& st2) const { return (!(*this == st2)); } private: /** rec_equal: Checks recursively whether or not two simplex trees are equal, using depth first search. */ - template - bool rec_equal(Siblings* s1, OtherSiblings* s2) { - if (s1->members().size() != s2->members().size()) return false; + template + bool rec_equal(Siblings const* s1, OtherSiblings const* s2) const { + if (s1->members().size() != s2->members().size()) + return false; auto sh2 = s2->members().begin(); - for (auto sh1 = s1->members().begin(); (sh1 != s1->members().end() && sh2 != s2->members().end()); ++sh1, ++sh2) { - if (sh1->first != sh2->first || !(sh1->second.filtration() == sh2->second.filtration())) return false; - if (has_children(sh1) != has_children(sh2)) return false; + for (auto sh1 = s1->members().begin(); + (sh1 != s1->members().end() && sh2 != s2->members().end()); + ++sh1, ++sh2) { + if (sh1->first != sh2->first || !(sh1->second.filtration() == sh2->second.filtration())) + return false; + if (has_children(sh1) != has_children(sh2)) + return false; // Recursivity on children only if both have children else if (has_children(sh1)) - if (!rec_equal(sh1->second.children(), sh2->second.children())) return false; + if (!rec_equal(sh1->second.children(), sh2->second.children())) + return false; } return true; } @@ -680,23 +737,34 @@ class Simplex_tree { * Same as `filtration()`, but does not handle `null_simplex()`. */ static const Filtration_value& filtration_(Simplex_handle sh) { - GUDHI_CHECK(sh != null_simplex(), "null simplex"); + GUDHI_CHECK (sh != null_simplex(), "null simplex"); return sh->second.filtration(); } + // Transform a Dictionary_const_it into a Dictionary_it + Dictionary_it _to_node_it(Simplex_handle sh) { + return self_siblings(sh)->to_non_const_it(sh); + } + public: /** \brief Returns the key associated to a simplex. * * If no key has been assigned, returns `null_key()`. * \pre SimplexTreeOptions::store_key */ - static Simplex_key key(Simplex_handle sh) { return sh->second.key(); } + static Simplex_key key(Simplex_handle sh) { + return sh->second.key(); + } /** \brief Returns the simplex that has index idx in the filtration. * * The filtration must be initialized. + * + * @note Not thread safe */ - Simplex_handle simplex(Simplex_key idx) const { return filtration_vect_[idx]; } + Simplex_handle simplex(Simplex_key idx) const { + return filtration_vect_[idx]; + } /** \brief Returns the filtration value of a simplex. * @@ -711,7 +779,47 @@ class Simplex_tree { } } - static Filtration_value& filtration_mutable(Simplex_handle sh) { return sh->second.filtration(); } + /** + * @brief Returns a non-const reference to the filtration value of a simplex. + * + * @pre Only available if @ref SimplexTreeOptions::store_filtration is true. + * + * @exception std::invalid_argument if sh is a @ref null_simplex. + */ + Filtration_value& get_filtration_value(Simplex_handle sh) { + static_assert(Options::store_filtration, + "A modifiable reference to a filtration value cannot be returned if no filtration value is stored."); + + if (sh == null_simplex()) { + throw std::invalid_argument("Cannot bind reference to filtration value of a null simplex."); + } + return _to_node_it(sh)->second.filtration(); + } + + /** + * @brief Returns a const reference to the filtration value of a simplex. + * + * @pre Only available if @ref SimplexTreeOptions::store_filtration is true. If false is necessary, + * use @ref filtration instead. + * + * @note It is a more strict variant of @ref filtration, as it will not compile if + * @ref SimplexTreeOptions::store_filtration is false and it will throw if the given simplex is a @ref null_simplex. + * + * @exception std::invalid_argument if sh is a @ref null_simplex. Note that @ref filtration returns + * infinity instead of throwing in this case. + */ + const Filtration_value& get_filtration_value(Simplex_handle sh) const { + static_assert( + Options::store_filtration, + "Filtration values are not stored. If you still want something to be returned, use `filtration(sh)` instead."); + + if (sh == null_simplex()) { + throw std::invalid_argument( + "Null simplices are not associated to filtration values. If you still want something to be returned, use " + "`filtration(sh)` instead."); + } + return sh->second.filtration(); + } /** \brief Sets the filtration value of a simplex. * \exception std::invalid_argument In debug mode, if sh is a null_simplex. @@ -719,20 +827,31 @@ class Simplex_tree { void assign_filtration(Simplex_handle sh, const Filtration_value& fv) { GUDHI_CHECK(sh != null_simplex(), std::invalid_argument("Simplex_tree::assign_filtration - cannot assign filtration on null_simplex")); - sh->second.assign_filtration(fv); + _to_node_it(sh)->second.assign_filtration(fv); } /** \brief Returns a Simplex_handle different from all Simplex_handles * associated to the simplices in the simplicial complex. * * One can call filtration(null_simplex()). */ - static Simplex_handle null_simplex() { return Dictionary_it(); } + static Simplex_handle null_simplex() { + return Dictionary_const_it(); + } /** \brief Returns a fixed number not in the interval [0, `num_simplices()`). */ - static Simplex_key null_key() { return -1; } + static Simplex_key null_key() { + return -1; + } + + /** \brief Returns the extra data stored in a simplex. */ + Simplex_data& simplex_data(Simplex_handle sh) { + GUDHI_CHECK(sh != null_simplex(), + std::invalid_argument("Simplex_tree::simplex_data - no data associated to null_simplex")); + return _to_node_it(sh)->second.data(); + } /** \brief Returns the extra data stored in a simplex. */ - static Simplex_data& simplex_data(Simplex_handle sh) { + const Simplex_data& simplex_data(Simplex_handle sh) const { GUDHI_CHECK(sh != null_simplex(), std::invalid_argument("Simplex_tree::simplex_data - no data associated to null_simplex")); return sh->second.data(); @@ -740,23 +859,31 @@ class Simplex_tree { /** \brief Returns a Vertex_handle different from all Vertex_handles associated * to the vertices of the simplicial complex. */ - Vertex_handle null_vertex() const { return null_vertex_; } + Vertex_handle null_vertex() const { + return null_vertex_; + } /** \brief Returns the number of vertices in the complex. */ - size_t num_vertices() const { return root_.members_.size(); } + size_t num_vertices() const { + return root_.members_.size(); + } /** \brief Returns whether the complex is empty. */ - bool is_empty() const { return root_.members_.empty(); } + bool is_empty() const { + return root_.members_.empty(); + } public: /** \brief Returns the number of simplices in the simplex_tree. * * This function takes time linear in the number of simplices. */ - size_t num_simplices() { return num_simplices(root()); } + size_t num_simplices() const { + return num_simplices(root()); + } private: /** \brief Returns the number of simplices in the simplex_tree. */ - size_t num_simplices(Siblings* sib) { + size_t num_simplices(const Siblings * sib) const { auto sib_begin = sib->members().begin(); auto sib_end = sib->members().end(); size_t simplices_number = sib->members().size(); @@ -774,7 +901,7 @@ class Simplex_tree { * @param curr_sib Pointer to the sibling container. * @return Height of the siblings in the tree (root counts as zero to make the height correspond to the dimension). */ - int dimension(Siblings* curr_sib) { + int dimension(Siblings const* curr_sib) const { int dim = -1; while (curr_sib != nullptr) { ++dim; @@ -785,10 +912,10 @@ class Simplex_tree { public: /** \brief Returns the number of simplices of each dimension in the simplex tree. */ - std::vector num_simplices_by_dimension() { + std::vector num_simplices_by_dimension() const { if (is_empty()) return {}; // std::min in case the upper bound got crazy - std::vector res(std::min(upper_bound_dimension() + 1, max_dimension() + 1)); + std::vector res(std::min(upper_bound_dimension()+1, max_dimension()+1)); auto fun = [&res](Simplex_handle, int dim) -> void { ++res[dim]; }; for_each_simplex(fun); if (dimension_to_be_lowered_) { @@ -798,7 +925,7 @@ class Simplex_tree { dimension_to_be_lowered_ = false; } else { GUDHI_CHECK(res.back() != 0, - std::logic_error("Bug in Gudhi: there is no simplex of dimension the dimension of the complex")); + std::logic_error("Bug in Gudhi: there is no simplex of dimension the dimension of the complex")); } return res; } @@ -806,23 +933,53 @@ class Simplex_tree { /** \brief Returns the dimension of a simplex. * * Must be different from null_simplex().*/ - int dimension(Simplex_handle sh) { return dimension(self_siblings(sh)); } + int dimension(Simplex_handle sh) const { + return dimension(self_siblings(sh)); + } - /** \brief Returns an upper bound on the dimension of the simplicial complex. */ - int upper_bound_dimension() const { return dimension_; } + /** \brief Returns an upper bound on the dimension of the simplicial complex. + * + * @note Not thread safe + */ + int upper_bound_dimension() const { + return dimension_; + } /** \brief Returns the dimension of the simplicial complex. - \details This function is not constant time because it can recompute dimension if required (can be triggered by - `remove_maximal_simplex()` or `prune_above_filtration()`). - */ - int dimension() { - if (dimension_to_be_lowered_) lower_upper_bound_dimension(); + * \details This function is not constant time because it can recompute dimension if required (can be triggered by + * `remove_maximal_simplex()` or `prune_above_filtration()`). + * + * @note Not thread safe + */ + int dimension() const { + if (dimension_to_be_lowered_) + lower_upper_bound_dimension(); return dimension_; } + /** + * @brief Returns the value stored as the number of parameters of the filtration values. + * The default value stored at construction of the simplex tree is 1. The user needs to set it by hand + * with @ref set_num_parameters if any other value is needed. + */ + int num_parameters() const { + return number_of_parameters_; + } + + /** + * @brief Stores the given value as number of parameters of the filtration values. + * At construction, the default number of parameters is set to 1. + * + * Note that there will not be any verification to ensure that the filtration values stored have this amount + * of parameters. + */ + void set_num_parameters(int new_number) { + number_of_parameters_ = new_number; + } + /** \brief Returns true if the node in the simplex tree pointed by * the given simplex handle has children.*/ - template + template bool has_children(SimplexHandle sh) const { // Here we rely on the root using null_vertex(), which cannot match any real vertex. return (sh->second.children()->parent() == sh->first); @@ -834,7 +991,7 @@ class Simplex_tree { /** \brief Returns the children of the node in the simplex tree pointed by sh. * \exception std::invalid_argument In debug mode, if sh has no child. */ - Siblings* children(Simplex_handle sh) const { + Siblings const* children(Simplex_handle sh) const { GUDHI_CHECK(has_children(sh), std::invalid_argument("Simplex_tree::children - argument has no child")); return sh->second.children(); } @@ -847,12 +1004,13 @@ class Simplex_tree { * The type InputVertexRange must be a range of Vertex_handle * on which we can call std::begin() function */ - template > - Simplex_handle find(const InputVertexRange& s) { + template> + Simplex_handle find(const InputVertexRange & s) const { auto first = std::begin(s); auto last = std::end(s); - if (first == last) return null_simplex(); // ----->> + if (first == last) + return null_simplex(); // ----->> // Copy before sorting std::vector copy(first, last); @@ -862,32 +1020,38 @@ class Simplex_tree { private: /** Find function, with a sorted range of vertices. */ - Simplex_handle find_simplex(const std::vector& simplex) { - Siblings* tmp_sib = &root_; - Dictionary_it tmp_dit; + Simplex_handle find_simplex(const std::vector & simplex) const { + Siblings const* tmp_sib = &root_; + Dictionary_const_it tmp_dit; auto vi = simplex.begin(); if constexpr (Options::contiguous_vertices && !Options::stable_simplex_handles) { // Equivalent to the first iteration of the normal loop GUDHI_CHECK(contiguous_vertices(), "non-contiguous vertices"); Vertex_handle v = *vi++; - if (v < 0 || v >= static_cast(root_.members_.size())) return null_simplex(); + if(v < 0 || v >= static_cast(root_.members_.size())) + return null_simplex(); tmp_dit = root_.members_.begin() + v; - if (vi == simplex.end()) return tmp_dit; - if (!has_children(tmp_dit)) return null_simplex(); + if (vi == simplex.end()) + return tmp_dit; + if (!has_children(tmp_dit)) + return null_simplex(); tmp_sib = tmp_dit->second.children(); } for (;;) { tmp_dit = tmp_sib->members_.find(*vi++); - if (tmp_dit == tmp_sib->members_.end()) return null_simplex(); - if (vi == simplex.end()) return tmp_dit; - if (!has_children(tmp_dit)) return null_simplex(); + if (tmp_dit == tmp_sib->members_.end()) + return null_simplex(); + if (vi == simplex.end()) + return tmp_dit; + if (!has_children(tmp_dit)) + return null_simplex(); tmp_sib = tmp_dit->second.children(); } } /** \brief Returns the Simplex_handle corresponding to the 0-simplex * representing the vertex with Vertex_handle v. */ - Simplex_handle find_vertex(Vertex_handle v) { + Simplex_handle find_vertex(Vertex_handle v) const { if constexpr (Options::contiguous_vertices && !Options::stable_simplex_handles) { assert(contiguous_vertices()); return root_.members_.begin() + v; @@ -921,28 +1085,28 @@ class Simplex_tree { * @param filtration_value Filtration value stored in the node. * @return Pair of the iterator to the new node and a boolean indicating if the insertion succeeded. */ - template - std::pair insert_node_(Siblings* sib, Vertex_handle v, Filt&& filtration_value) { - std::pair ins = sib->members_.try_emplace(v, sib, std::forward(filtration_value)); + template + std::pair insert_node_(Siblings *sib, Vertex_handle v, Filt&& filtration_value) { + std::pair ins = sib->members_.try_emplace(v, sib, std::forward(filtration_value)); - if constexpr (update_children) { + if constexpr (update_children){ if (!(has_children(ins.first))) { ins.first->second.assign_children(new Siblings(sib, v)); } } - if (ins.second) { + if (ins.second){ // Only required when insertion is successful update_simplex_tree_after_node_insertion(ins.first); return ins; } - if constexpr (Options::store_filtration && update_fil) { + if constexpr (Options::store_filtration && update_fil){ if (unify_lifetimes(ins.first->second.filtration(), filtration_value)) return ins; } - if constexpr (set_to_null) { - ins.first = null_simplex(); + if constexpr (set_to_null){ + ins.first = Dictionary_it(); } return ins; @@ -962,12 +1126,12 @@ class Simplex_tree { * output pair to the Simplex_handle of the simplex. Otherwise, we set the Simplex_handle part to * null_simplex. * - */ + */ template > std::pair insert_simplex_raw(const RandomVertexHandleRange& simplex, const Filtration_value& filtration) { - Siblings* curr_sib = &root_; - std::pair res_insert; + Siblings * curr_sib = &root_; + std::pair res_insert; auto vi = simplex.begin(); for (; vi != std::prev(simplex.end()); ++vi) { @@ -978,7 +1142,7 @@ class Simplex_tree { GUDHI_CHECK(*vi != null_vertex(), "cannot use the dummy null_vertex() as a real vertex"); res_insert = insert_node_(curr_sib, *vi, filtration); - if (res_insert.second) { + if (res_insert.second){ int dim = static_cast(boost::size(simplex)) - 1; if (dim > dimension_) { // Update dimension if needed @@ -1012,13 +1176,14 @@ class Simplex_tree { * * The type InputVertexRange must be a range for which .begin() and * .end() return input iterators, with 'value_type' Vertex_handle. */ - template > - std::pair insert_simplex(const InputVertexRange& simplex, + template> + std::pair insert_simplex(const InputVertexRange & simplex, const Filtration_value& filtration = Filtration_value()) { auto first = std::begin(simplex); auto last = std::end(simplex); - if (first == last) return std::pair(null_simplex(), true); // ----->> + if (first == last) + return std::pair(null_simplex(), true); // ----->> // Copy before sorting std::vector copy(first, last); @@ -1026,23 +1191,78 @@ class Simplex_tree { return insert_simplex_raw(copy, filtration); } - /** \brief Insert a N-simplex and all his subfaces, from a N-simplex represented by a range of + /** + * @brief List of insertion strategies for @ref insert_simplex_and_subfaces, which takes a simplex \f$ \sigma \f$ + * and a filtration value \f$ f \f$ as argument. + */ + enum class Insertion_strategy : std::uint8_t { + /** + * @brief Let \f$ f \f$ be the filtration value given as argument. Inserts the simplex \f$ \sigma \f$ as follows: + * - to \f$ f \f$, if \f$ \sigma \f$ didn't existed yet, + * - if \f$ \sigma \f$ was already inserted, let \f$ f' \f$ be its filtration value. The new filtration value will + * be @ref FiltrationValue::unify_lifetimes (\f$ f \f$, \f$ f' \f$). + * + * Then, the filtration values of all faces of \f$ \sigma \f$ are pushed to the union of their old value and of + * the new value of \f$ \sigma \f$. + */ + LOWEST, + /** + * @brief Let \f$ f \f$ be the filtration value given as argument. Inserts the simplex \f$ \sigma \f$ as follows: + * - if \f$ \sigma \f$ was not inserted yet, then \f$ \sigma \f$ and all its faces, which are not already included + * in the complex, are inserted at either \f$ f \f$ or at the first possible filtration value when \f$ f \f$ is too + * low (to insure the validity of the filtration). This is computed with @ref FiltrationValue::intersect_lifetimes. + * - if \f$ \sigma \f$ existed already, then nothing is done. + */ + HIGHEST, + /** + * @brief Ignores the filtration value given as argument and inserts the simplex and all its faces as soon as + * the already inserted faces allows it. That is, the filtration value of a not already inserted face will be + * the "intersection" of the filtration values of all it facets, computed with + * @ref FiltrationValue::intersect_lifetimes "". If none of the faces were already inserted, everything will simply + * be placed at minus infinity. Therefore, this strategy is equivalent to @ref Insertion_strategy::HIGHEST with + * the given filtration value as minus infinity. + */ + FIRST_POSSIBLE, + /** + * @brief If the simplex to insert: + * - already exists in the simplex tree, its filtration value is replaced by the new given one, + * - does not exists yet, it and all its non-existing faces are inserted at the given filtration value, + * none of the already inserted faces are touched. + * + * This option is mainly usefull when the methods @ref FiltrationValue::intersect_lifetimes or + * @ref FiltrationValue::unify_lifetimes are heavy for the associated filtration value class and the user + * wants to avoid calling them just to ensure a valid filtration. It is therefore **responsibility of the user to + * ensure a valid filtration** at the end of the construction, before any filtration related method is used + * (@ref filtration_simplex_range for example). + */ + FORCE + }; + + /** + * @brief Insert a N-simplex and all his subfaces, from a N-simplex represented by a range of * Vertex_handles, in the simplicial complex. - * - * @param[in] Nsimplex range of Vertex_handles, representing the vertices of the new N-simplex - * @param[in] filtration the filtration value assigned to the new N-simplex. + * + * @tparam InputVertexRange Range of @ref Vertex_handle. + * @param[in] Nsimplex Vertices of the new N-simplex. + * @param[in] filtration Ignored if @ref SimplexTreeOptions::store_filtration is false. + * Otherwise, see `insertion_strategy` below. Default value: default constructor. + * @param insertion_strategy Ignored if @ref SimplexTreeOptions::store_filtration is false. + * Indicates where to insert the simplex and its faces in the filtration with respect to the value given by + * `filtration`. See @ref Insertion_strategy for more details. Default value: @ref Insertion_strategy::LOWEST. * @return If the new simplex is inserted successfully (i.e. it was not in the * simplicial complex yet) the bool is set to true and the Simplex_handle is the handle assigned * to the new simplex. - * If the insertion fails (the simplex is already there), the bool is set to false. If the insertion - * fails and the simplex already in the complex has a filtration value strictly bigger than 'filtration', - * we assign this simplex with the new value 'filtration', and set the Simplex_handle field of the - * output pair to the Simplex_handle of the simplex. Otherwise, we set the Simplex_handle part to - * null_simplex. + * If the insertion fails (the simplex is already there), the bool is set to false. In that case, the Simplex_handle + * part is set to: + * - null_simplex, if the filtration value of the simplex is not modified, + * - the simplex handle assigned to the simplex, if the filtration value of the simplex is modified. */ template > - std::pair insert_simplex_and_subfaces(const InputVertexRange& Nsimplex, - const Filtration_value& filtration = Filtration_value()) { + std::pair insert_simplex_and_subfaces( + const InputVertexRange& Nsimplex, + const Filtration_value& filtration = Filtration_value(), + Insertion_strategy insertion_strategy = Insertion_strategy::LOWEST) + { auto first = std::begin(Nsimplex); auto last = std::end(Nsimplex); @@ -1055,86 +1275,145 @@ class Simplex_tree { auto last_unique = std::unique(copy.begin(), copy.end()); copy.erase(last_unique, copy.end()); GUDHI_CHECK_code(for (Vertex_handle v : copy) - GUDHI_CHECK(v != null_vertex(), "cannot use the dummy null_vertex() as a real vertex");) - // Update dimension if needed. We could wait to see if the insertion succeeds, but I doubt there is much to - // gain. - dimension_ = (std::max)(dimension_, static_cast(std::distance(copy.begin(), copy.end())) - 1); - - auto truc = rec_insert_simplex_and_subfaces_sorted(root(), copy.begin(), copy.end(), filtration); - if constexpr (SimplexTreeOptions::is_multi_parameter && !Filtration_value::is_multicritical()) { - make_subfiltration_non_decreasing(truc.first); + GUDHI_CHECK(v != null_vertex(), "cannot use the dummy null_vertex() as a real vertex");); + // Update dimension if needed. We could wait to see if the insertion succeeds, but I doubt there is much to gain. + dimension_ = (std::max)(dimension_, static_cast(std::distance(copy.begin(), copy.end())) - 1); + + if constexpr (Options::store_filtration){ + switch (insertion_strategy) { + case Insertion_strategy::LOWEST: + return _rec_insert_simplex_and_subfaces_sorted(root(), copy.begin(), copy.end(), filtration); + case Insertion_strategy::HIGHEST: + return _insert_simplex_and_subfaces_at_highest(root(), copy.begin(), copy.end(), filtration); + case Insertion_strategy::FIRST_POSSIBLE: + return _insert_simplex_and_subfaces_at_highest( + root(), copy.begin(), copy.end(), Filtration_simplex_base_real::get_minus_infinity()); + case Insertion_strategy::FORCE: + return _insert_simplex_and_subfaces_forcing_filtration_value(root(), copy.begin(), copy.end(), filtration); + default: + throw std::invalid_argument("Given insertion strategy is not available."); + } + } else { + return _rec_insert_simplex_and_subfaces_sorted(root(), copy.begin(), copy.end(), filtration); } - return truc; } private: // To insert {1,2,3,4}, we insert {2,3,4} twice, once at the root, and once below 1. - template - std::pair rec_insert_simplex_and_subfaces_sorted(Siblings* sib, - ForwardVertexIterator first, - ForwardVertexIterator last, - const Filtration_value& filt) { + template + std::pair _rec_insert_simplex_and_subfaces_sorted(Siblings* sib, + ForwardVertexIterator first, + ForwardVertexIterator last, + const Filtration_value& filt) { // An alternative strategy would be: // - try to find the complete simplex, if found (and low filtration) exit // - insert all the vertices at once in sib // - loop over those (new or not) simplices, with a recursive call(++first, last) Vertex_handle vertex_one = *first; - if (++first == last) { - if constexpr (SimplexTreeOptions::is_multi_parameter) { - return insert_node_(sib, vertex_one, filt); - } else { - return insert_node_(sib, vertex_one, filt); - } - } + // insert_node_(sib, ...) + // update_fil: if true, calls `unify_lifetimes` on the new and old filtration value of the node + // update_children: if true, assign a child to the node if he didn't had one + // set_to_null: if true, sets returned iterator to null simplex + + if (++first == last) return insert_node_(sib, vertex_one, filt); // TODO: have special code here, we know we are building the whole subtree from scratch. - Node node; - if constexpr (SimplexTreeOptions::is_multi_parameter) { - node = insert_node_(sib, vertex_one, filt).first->second; + auto insertion_result = insert_node_(sib, vertex_one, filt); + + auto res = _rec_insert_simplex_and_subfaces_sorted( + insertion_result.first->second.children(), first, last, filt); + // No need to continue if the full simplex was already there with a low enough filtration value. + if (res.first != null_simplex()) + _rec_insert_simplex_and_subfaces_sorted(sib, first, last, filt); + return res; + } + + void _make_subfiltration_non_decreasing(Simplex_handle sh) { + Filtration_value& f = get_filtration_value(sh); + for (auto sh_b : boundary_simplex_range(sh)) { + _make_subfiltration_non_decreasing(sh_b); + intersect_lifetimes(f, filtration(sh_b)); + } + } + + template + std::pair _insert_simplex_and_subfaces_at_highest(Siblings* sib, + ForwardVertexIterator first, + ForwardVertexIterator last, + const Filtration_value& filt) { + auto res = _rec_insert_simplex_and_subfaces_sorted(sib, first, last, filt); + if (res.second) { + _make_subfiltration_non_decreasing(res.first); } else { - node = insert_node_(sib, vertex_one, filt).first->second; + res.first = null_simplex(); } + return res; + } - auto res = rec_insert_simplex_and_subfaces_sorted(node.children(), first, last, filt); - // No need to continue if the full simplex was already there with a low enough filtration value. - if (res.first != null_simplex()) rec_insert_simplex_and_subfaces_sorted(sib, first, last, filt); + template + std::pair _insert_simplex_and_subfaces_forcing_filtration_value(Siblings* sib, + ForwardVertexIterator first, + ForwardVertexIterator last, + const Filtration_value& filt) { + auto res = _rec_insert_simplex_and_subfaces_sorted(sib, first, last, filt); + if (!res.second) { + Filtration_value& f = _to_node_it(res.first)->second.filtration(); + // could handle the case were f == filt to set res.first to null_simplex, but I don't think it the point of + // this strategy and it seems better to avoid `operator==` which is also not trivial for vectors etc. + f = filt; + } return res; } public: /** \brief Assign a value 'key' to the key of the simplex * represented by the Simplex_handle 'sh'. */ - void assign_key(Simplex_handle sh, Simplex_key key) { sh->second.assign_key(key); } + void assign_key(Simplex_handle sh, Simplex_key key) { + _to_node_it(sh)->second.assign_key(key); + } /** Returns the two Simplex_handle corresponding to the endpoints of * and edge. sh must point to a 1-dimensional simplex. This is an * optimized version of the boundary computation. */ - std::pair endpoints(Simplex_handle sh) { + std::pair endpoints(Simplex_handle sh) const { assert(dimension(sh) == 1); - return {find_vertex(sh->first), find_vertex(self_siblings(sh)->parent())}; + return { find_vertex(sh->first), find_vertex(self_siblings(sh)->parent()) }; } /** Returns the Siblings containing a simplex.*/ - template - static Siblings* self_siblings(SimplexHandle sh) { + template + Siblings const* self_siblings(SimplexHandle sh) const { if (sh->second.children()->parent() == sh->first) return sh->second.children()->oncles(); else return sh->second.children(); } + /** Returns the Siblings containing a simplex.*/ + template + Siblings* self_siblings(SimplexHandle sh) { + // Scott Meyers in Effective C++ 3rd Edition. On page 23, Item 3: a non const method can safely call a const one + // Doing it the other way is not safe + return const_cast(std::as_const(*this).self_siblings(sh)); + } + public: /** Returns a pointer to the root nodes of the simplex tree. */ Siblings* root() { return &root_; } + /** Returns a pointer to the root nodes of the simplex tree. */ + const Siblings * root() const { + return &root_; + } + /** \brief Set a dimension for the simplicial complex. * \details * If `exact` is false, `dimension` is only an upper bound on the dimension of the complex. * This function must be used with caution because it disables or limits the on-demand recomputation of the dimension * (the need for recomputation can be caused by `remove_maximal_simplex()` or `prune_above_filtration()`). */ - void set_dimension(int dimension, bool exact = true) { + void set_dimension(int dimension, bool exact=true) { dimension_to_be_lowered_ = !exact; dimension_ = dimension; } @@ -1147,7 +1426,7 @@ class Simplex_tree { * It defines a StrictWeakOrdering on simplices. The Simplex_vertex_iterators * must traverse the Vertex_handle in decreasing order. Reverse lexicographic order satisfy * the property that a subsimplex of a simplex is always strictly smaller with this order. */ - bool reverse_lexicographic_order(Simplex_handle sh1, Simplex_handle sh2) { + bool reverse_lexicographic_order(Simplex_handle sh1, Simplex_handle sh2) const { Simplex_vertex_range rg1 = simplex_vertex_range(sh1); Simplex_vertex_range rg2 = simplex_vertex_range(sh2); Simplex_vertex_iterator it1 = rg1.begin(); @@ -1170,7 +1449,8 @@ class Simplex_tree { * Reverse lexicographic order has the property to always consider the subsimplex of a simplex * to be smaller. The filtration function must be monotonic. */ struct is_before_in_totally_ordered_filtration { - explicit is_before_in_totally_ordered_filtration(Simplex_tree* st) : st_(st) {} + explicit is_before_in_totally_ordered_filtration(Simplex_tree const* st) + : st_(st) { } bool operator()(const Simplex_handle sh1, const Simplex_handle sh2) const { // Not using st_->filtration(sh1) because it uselessly tests for null_simplex. @@ -1181,29 +1461,32 @@ class Simplex_tree { return st_->reverse_lexicographic_order(sh1, sh2); } - Simplex_tree* st_; + Simplex_tree const* st_; }; public: /** \brief Initializes the filtration cache, i.e. sorts the * simplices according to their order in the filtration. * Assumes that the filtration values have a total order. - * If not, please use @ref initialize_filtration(Comparator&&, bool) instead. + * If not, please use @ref initialize_filtration(Comparator&&, Ignorer&&) const instead. * Two simplices with same filtration value are ordered by a reverse lexicographic order. * * It always recomputes the cache, even if one already exists. * * Any insertion, deletion or change of filtration value invalidates this cache, * which can be cleared with clear_filtration(). + * + * @note Not thread safe */ - void initialize_filtration(bool ignore_infinite_values = false) { - if (ignore_infinite_values) { + void initialize_filtration(bool ignore_infinite_values = false) const { + if (ignore_infinite_values){ initialize_filtration(is_before_in_totally_ordered_filtration(this), [&](Simplex_handle sh) -> bool { - return filtration(sh) == Filtration_simplex_base_real::get_infinity(); + return is_positive_infinity(filtration(sh)); }); } else { - initialize_filtration(is_before_in_totally_ordered_filtration(this), - [](Simplex_handle) -> bool { return false; }); + initialize_filtration(is_before_in_totally_ordered_filtration(this), [](Simplex_handle) -> bool { + return false; + }); } } @@ -1216,11 +1499,11 @@ class Simplex_tree { * lexicographic order. This would generate a valid 1-parameter filtration which can than be interpreted as a line * in the 2-parameter module and used with @ref filtration_simplex_range "". Also, the persistence along this line * can than be computed with @ref Gudhi::persistent_cohomology::Persistent_cohomology "Persistent_cohomology". - * Just note that it is important to call @ref initialize_filtration(Comparator&&, bool) explicitly before call - * @ref Gudhi::persistent_cohomology::Persistent_cohomology::compute_persistent_cohomology + * Just note that it is important to call @ref initialize_filtration(Comparator&&, Ignorer&&) const explicitly before + * call @ref Gudhi::persistent_cohomology::Persistent_cohomology::compute_persistent_cohomology * "Persistent_cohomology::compute_persistent_cohomology" in that case, otherwise, the computation of persistence - * will fail (if no call to @ref initialize_filtration() or @ref initialize_filtration(Comparator&&, bool) was made, - * it will call it it-self with no arguments and the simplices will be wrongly sorted). + * will fail (if no call to @ref initialize_filtration() or @ref initialize_filtration(Comparator&&, Ignorer&&) const + * was made, it will call it it-self with no arguments and the simplices will be wrongly sorted). * * It always recomputes the cache, even if one already exists. * @@ -1237,9 +1520,11 @@ class Simplex_tree { * the resulting filtration is still a valid filtration, that is all proper faces of a not ignored simplex * should also not be ignored and have to appear before this simplex. There will be no tests to ensure that, so * it is of the users responsibility. + * + * @note Not thread safe */ - template - void initialize_filtration(Comparator&& is_before_in_filtration, Ignorer&& ignore_simplex) { + template + void initialize_filtration(Comparator&& is_before_in_filtration, Ignorer&& ignore_simplex) const { filtration_vect_.clear(); filtration_vect_.reserve(num_simplices()); for (Simplex_handle sh : complex_simplex_range()) { @@ -1247,15 +1532,6 @@ class Simplex_tree { filtration_vect_.push_back(sh); } - /* We use stable_sort here because with libstdc++ it is faster than sort. - * is_before_in_filtration is now a total order, but we used to call - * stable_sort for the following heuristic: - * The use of a depth-first traversal of the simplex tree, provided by - * complex_simplex_range(), combined with a stable sort is meant to - * optimize the order of simplices with same filtration value. The - * heuristic consists in inserting the cofaces of a simplex as soon as - * possible. - */ #ifdef GUDHI_USE_TBB tbb::parallel_sort(filtration_vect_.begin(), filtration_vect_.end(), is_before_in_filtration); #else @@ -1265,18 +1541,25 @@ class Simplex_tree { /** \brief Initializes the filtration cache if it isn't initialized yet. * - * Automatically called by filtration_simplex_range(). */ - void maybe_initialize_filtration() { + * Automatically called by filtration_simplex_range(). + * + * @note Not thread safe + */ + void maybe_initialize_filtration() const { if (filtration_vect_.empty()) { initialize_filtration(); } } - /** \brief Clears the filtration cache produced by initialize_filtration(). * * Useful when initialize_filtration() has already been called and we perform an operation - * (say an insertion) that invalidates the cache. */ - void clear_filtration() { filtration_vect_.clear(); } + * (say an insertion) that invalidates the cache. + * + * @note Not thread safe + */ + void clear_filtration() const { + filtration_vect_.clear(); + } private: /** Recursive search of cofaces @@ -1287,17 +1570,13 @@ class Simplex_tree { *\param cofaces contains a list of Simplex_handle, representing all the cofaces asked. *\param star true if we need the star of the simplex *\param nbVertices number of vertices of the cofaces we search - * Prefix actions : When the bottom vertex matches with the current vertex in the tree, we remove the bottom vertex - *from vertices. Infix actions : Then we call or not the recursion. Postfix actions : Finally, we add back the removed - *vertex into vertices, and remove this vertex from curr_nbVertices so that we didn't change the parameters. If the - *vertices list is empty, we need to check if curr_nbVertices matches with the dimension of the cofaces asked. + * Prefix actions : When the bottom vertex matches with the current vertex in the tree, we remove the bottom vertex from vertices. + * Infix actions : Then we call or not the recursion. + * Postfix actions : Finally, we add back the removed vertex into vertices, and remove this vertex from curr_nbVertices so that we didn't change the parameters. + * If the vertices list is empty, we need to check if curr_nbVertices matches with the dimension of the cofaces asked. */ - void rec_coface(std::vector& vertices, - Siblings* curr_sib, - int curr_nbVertices, - std::vector& cofaces, - bool star, - int nbVertices) { + void rec_coface(std::vector &vertices, Siblings const*curr_sib, int curr_nbVertices, + std::vector& cofaces, bool star, int nbVertices) const { if (!(star || curr_nbVertices <= nbVertices)) // dimension of actual simplex <= nbVertices return; for (Simplex_handle simplex = curr_sib->members().begin(); simplex != curr_sib->members().end(); ++simplex) { @@ -1307,7 +1586,8 @@ class Simplex_tree { // Add a coface if we want the star or if the number of vertices of the current simplex matches with nbVertices bool addCoface = (star || curr_nbVertices == nbVertices); - if (addCoface) cofaces.push_back(simplex); + if (addCoface) + cofaces.push_back(simplex); if ((!addCoface || star) && has_children(simplex)) // Rec call rec_coface(vertices, simplex->second.children(), curr_nbVertices + 1, cofaces, star, nbVertices); } else { @@ -1315,7 +1595,8 @@ class Simplex_tree { // If curr_sib matches with the top vertex bool equalDim = (star || curr_nbVertices == nbVertices); // dimension of actual simplex == nbVertices bool addCoface = vertices.size() == 1 && equalDim; - if (addCoface) cofaces.push_back(simplex); + if (addCoface) + cofaces.push_back(simplex); if ((!addCoface || star) && has_children(simplex)) { // Rec call Vertex_handle tmp = vertices.back(); @@ -1343,7 +1624,9 @@ class Simplex_tree { * Simplex_tree::Simplex_handle range for an optimized search for the star of a simplex when * SimplexTreeOptions::link_nodes_by_label is true. */ - Cofaces_simplex_range star_simplex_range(const Simplex_handle simplex) { return cofaces_simplex_range(simplex, 0); } + Cofaces_simplex_range star_simplex_range(const Simplex_handle simplex) const { + return cofaces_simplex_range(simplex, 0); + } /** \brief Compute the cofaces of a n simplex * \param simplex represent the n-simplex of which we search the n+codimension cofaces @@ -1355,7 +1638,7 @@ class Simplex_tree { * Simplex_tree::Simplex_handle range for an optimized search for the coface of a simplex when * SimplexTreeOptions::link_nodes_by_label is true. */ - Cofaces_simplex_range cofaces_simplex_range(const Simplex_handle simplex, int codimension) { + Cofaces_simplex_range cofaces_simplex_range(const Simplex_handle simplex, int codimension) const{ // codimension must be positive or null integer assert(codimension >= 0); @@ -1392,8 +1675,7 @@ class Simplex_tree { * * Inserts all vertices and edges given by a OneSkeletonGraph. * OneSkeletonGraph must be a model of - * boost::VertexAndEdgeListGraph + * boost::VertexAndEdgeListGraph * and boost::PropertyGraph. * * The vertex filtration value is accessible through the property tag @@ -1409,7 +1691,7 @@ class Simplex_tree { * * If an edge appears with multiplicity, the function will arbitrarily pick * one representative to read the filtration value. */ - template + template void insert_graph(const OneSkeletonGraph& skel_graph) { // the simplex tree must be empty assert(num_simplices() == 0); @@ -1427,10 +1709,9 @@ class Simplex_tree { } if constexpr (!Options::stable_simplex_handles) - root_.members_.reserve(num_vertices(skel_graph)); // probably useless in most cases - auto verts = vertices(skel_graph) | boost::adaptors::transformed([&](auto v) { - return Dit_value_t(v, Node(&root_, get(vertex_filtration_t(), skel_graph, v))); - }); + root_.members_.reserve(num_vertices(skel_graph)); // probably useless in most cases + auto verts = vertices(skel_graph) | boost::adaptors::transformed([&](auto v){ + return Dit_value_t(v, Node(&root_, get(vertex_filtration_t(), skel_graph, v))); }); root_.members_.insert(boost::begin(verts), boost::end(verts)); // This automatically sorts the vertices, the graph concept doesn't guarantee the order in which we iterate. @@ -1439,8 +1720,7 @@ class Simplex_tree { } std::pair::edge_iterator, - typename boost::graph_traits::edge_iterator> - boost_edges = edges(skel_graph); + typename boost::graph_traits::edge_iterator> boost_edges = edges(skel_graph); // boost_edges.first is the equivalent to boost_edges.begin() // boost_edges.second is the equivalent to boost_edges.end() for (; boost_edges.first != boost_edges.second; boost_edges.first++) { @@ -1456,7 +1736,7 @@ class Simplex_tree { // Should we actually forbid multiple edges? That would be consistent // with rejecting self-loops. if (v < u) std::swap(u, v); - auto sh = find_vertex(u); + auto sh = _to_node_it(find_vertex(u)); if (!has_children(sh)) { sh->second.assign_children(new Siblings(&root_, sh->first)); } @@ -1472,15 +1752,17 @@ class Simplex_tree { * This may be faster than inserting the vertices one by one, especially in a random order. * The complex does not need to be empty before calling this function. However, if a vertex is * already present, its filtration value is not modified, unlike with other insertion functions. */ - template + template > void insert_batch_vertices(VertexRange const& vertices, const Filtration_value& filt = Filtration_value()) { - auto verts = vertices | boost::adaptors::transformed([&](auto v) { return Dit_value_t(v, Node(&root_, filt)); }); + auto verts = vertices | boost::adaptors::transformed([&](auto v){ + return Dit_value_t(v, Node(&root_, filt)); }); root_.members_.insert(boost::begin(verts), boost::end(verts)); if (dimension_ < 0 && !root_.members_.empty()) dimension_ = 0; if constexpr (Options::link_nodes_by_label) { for (auto sh = root_.members().begin(); sh != root_.members().end(); sh++) { // update newly inserted simplex (the one that are not linked) - if (!sh->second.list_max_vertex_hook_.is_linked()) update_simplex_tree_after_node_insertion(sh); + if (!sh->second.list_max_vertex_hook_.is_linked()) + update_simplex_tree_after_node_insertion(sh); } } } @@ -1496,57 +1778,59 @@ class Simplex_tree { * * The Simplex_tree must contain no simplex of dimension bigger than * 1 when calling the method. */ - void expansion(int max_dim) { - if (max_dim <= 1) return; - clear_filtration(); // Drop the cache. - dimension_ = max_dim; - for (Dictionary_it root_it = root_.members_.begin(); root_it != root_.members_.end(); ++root_it) { + void expansion(int max_dimension) { + if (max_dimension <= 1) return; + clear_filtration(); // Drop the cache. + dimension_ = max_dimension; + for (Dictionary_it root_it = root_.members_.begin(); + root_it != root_.members_.end(); ++root_it) { if (has_children(root_it)) { - siblings_expansion(root_it->second.children(), max_dim - 1); + siblings_expansion(root_it->second.children(), max_dimension - 1); } } - dimension_ = max_dim - dimension_; + dimension_ = max_dimension - dimension_; } /** - * @brief Adds a new vertex or a new edge in a flag complex, as well as all - * simplices of its star, defined to maintain the property - * of the complex to be a flag complex, truncated at dimension dim_max. - * To insert a new edge, the two given vertex handles have to correspond - * to the two end points of the edge. To insert a new vertex, the handles - * have to be twice the same and correspond to the number you want assigned - * to it. I.e., to insert vertex \f$i\f$, give \f$u = v = i\f$. - * The method assumes that the given edge was not already contained in - * the simplex tree, so the behaviour is undefined if called on an existing - * edge. Also, the vertices of an edge have to be inserted before the edge. - * - * @param[in] u,v Vertex_handle representing the new edge - * (@p v != @p u) or the new vertex (@p v == @p u). - * @param[in] fil Filtration value of the edge. - * @param[in] dim_max Maximal dimension of the expansion. - * If set to -1, the expansion goes as far as possible. - * @param[out] added_simplices Contains at the end all new - * simplices induced by the insertion of the edge. - * The container is not emptied and new simplices are - * appended at the end. - * - * @pre `SimplexTreeOptions::link_nodes_by_label` must be true. - * @pre When inserting the edge `[u,v]`, the vertices @p u and @p v have to be - * already inserted in the simplex tree. - * - * @warning If the edges and vertices are not inserted in the order of their - * filtration values, the method `make_filtration_non_decreasing()` has to be - * called at the end of the insertions to restore the intended filtration. - * Note that even then, an edge has to be inserted after its vertices. - * @warning The method assumes that the given edge or vertex was not already - * contained in the simplex tree, so the behaviour is undefined if called on - * an existing simplex. - */ - void insert_edge_as_flag(Vertex_handle u, - Vertex_handle v, - const Filtration_value& fil, - int dim_max, - std::vector& added_simplices) { + * @brief Adds a new vertex or a new edge in a flag complex, as well as all + * simplices of its star, defined to maintain the property + * of the complex to be a flag complex, truncated at dimension dim_max. + * To insert a new edge, the two given vertex handles have to correspond + * to the two end points of the edge. To insert a new vertex, the handles + * have to be twice the same and correspond to the number you want assigned + * to it. I.e., to insert vertex \f$i\f$, give \f$u = v = i\f$. + * The method assumes that the given edge was not already contained in + * the simplex tree, so the behaviour is undefined if called on an existing + * edge. Also, the vertices of an edge have to be inserted before the edge. + * + * @param[in] u,v Vertex_handle representing the new edge + * (@p v != @p u) or the new vertex (@p v == @p u). + * @param[in] fil Filtration value of the edge. + * @param[in] dim_max Maximal dimension of the expansion. + * If set to -1, the expansion goes as far as possible. + * @param[out] added_simplices Contains at the end all new + * simplices induced by the insertion of the edge. + * The container is not emptied and new simplices are + * appended at the end. + * + * @pre `SimplexTreeOptions::link_nodes_by_label` must be true. + * @pre When inserting the edge `[u,v]`, the vertices @p u and @p v have to be + * already inserted in the simplex tree. + * + * @warning If the edges and vertices are not inserted in the order of their + * filtration values, the method `make_filtration_non_decreasing()` has to be + * called at the end of the insertions to restore the intended filtration. + * Note that even then, an edge has to be inserted after its vertices. + * @warning The method assumes that the given edge or vertex was not already + * contained in the simplex tree, so the behaviour is undefined if called on + * an existing simplex. + */ + void insert_edge_as_flag( Vertex_handle u + , Vertex_handle v + , const Filtration_value& fil + , int dim_max + , std::vector& added_simplices) + { /** * In term of edges in the graph, inserting edge `[u,v]` only affects * the subtree rooted at @p u. @@ -1568,75 +1852,79 @@ class Simplex_tree { static_assert(Options::link_nodes_by_label, "Options::link_nodes_by_label must be true"); - if (u == v) { // Are we inserting a vertex? + if (u == v) { // Are we inserting a vertex? auto res_ins = insert_node_(&root_, u, fil); - if (res_ins.second) { // if the vertex is not in the complex, insert it - added_simplices.push_back(res_ins.first); // no more insert in root_.members() + if (res_ins.second) { //if the vertex is not in the complex, insert it + added_simplices.push_back(res_ins.first); //no more insert in root_.members() if (dimension_ == -1) dimension_ = 0; } - return; // because the vertex is isolated, no more insertions. + return; //because the vertex is isolated, no more insertions. } // else, we are inserting an edge: ensure that u < v - if (v < u) { - std::swap(u, v); - } + if (v < u) { std::swap(u,v); } - // Note that we copy Simplex_handle (aka map iterators) in added_simplices - // while we are still modifying the Simplex_tree. Insertions in siblings may - // invalidate Simplex_handles; we take care of this fact by first doing all - // insertion in a Sibling, then inserting all handles in added_simplices. + //Note that we copy Simplex_handle (aka map iterators) in added_simplices + //while we are still modifying the Simplex_tree. Insertions in siblings may + //invalidate Simplex_handles; we take care of this fact by first doing all + //insertion in a Sibling, then inserting all handles in added_simplices. #ifdef GUDHI_DEBUG - // check whether vertices u and v are in the tree. If not, return an error. + //check whether vertices u and v are in the tree. If not, return an error. auto sh_u = root_.members().find(u); - GUDHI_CHECK(sh_u != root_.members().end() && root_.members().find(v) != root_.members().end(), - std::invalid_argument( - "Simplex_tree::insert_edge_as_flag - inserts an edge whose vertices are not in the complex")); - GUDHI_CHECK( - !has_children(sh_u) || sh_u->second.children()->members().find(v) == sh_u->second.children()->members().end(), - std::invalid_argument("Simplex_tree::insert_edge_as_flag - inserts an already existing edge")); + GUDHI_CHECK(sh_u != root_.members().end() && + root_.members().find(v) != root_.members().end(), + std::invalid_argument( + "Simplex_tree::insert_edge_as_flag - inserts an edge whose vertices are not in the complex") + ); + GUDHI_CHECK(!has_children(sh_u) || + sh_u->second.children()->members().find(v) == sh_u->second.children()->members().end(), + std::invalid_argument( + "Simplex_tree::insert_edge_as_flag - inserts an already existing edge") + ); #endif // to update dimension const auto tmp_dim = dimension_; auto tmp_max_dim = dimension_; - // for all siblings containing a Node labeled with u (including the root), run - // compute_punctual_expansion - // todo parallelize - List_max_vertex* nodes_with_label_u = nodes_by_label(u); // all Nodes with u label + //for all siblings containing a Node labeled with u (including the root), run + //compute_punctual_expansion + //todo parallelize + List_max_vertex* nodes_with_label_u = nodes_by_label(u);//all Nodes with u label GUDHI_CHECK(nodes_with_label_u != nullptr, "Simplex_tree::insert_edge_as_flag - cannot find the list of Nodes with label u"); - for (auto&& node_as_hook : *nodes_with_label_u) { - Node& node_u = static_cast(node_as_hook); // corresponding node, has label u + for (auto&& node_as_hook : *nodes_with_label_u) + { + Node& node_u = static_cast(node_as_hook); //corresponding node, has label u Simplex_handle sh_u = simplex_handle_from_node(node_u); Siblings* sib_u = self_siblings(sh_u); - if (sib_u->members().find(v) != sib_u->members().end()) { // v is the label of a sibling of node_u + if (sib_u->members().find(v) != sib_u->members().end()) { //v is the label of a sibling of node_u int curr_dim = dimension(sib_u); - if (dim_max == -1 || curr_dim < dim_max) { + if (dim_max == -1 || curr_dim < dim_max){ if (!has_children(sh_u)) { - // then node_u was a leaf and now has a new child Node labeled v - // the child v is created in compute_punctual_expansion + //then node_u was a leaf and now has a new child Node labeled v + //the child v is created in compute_punctual_expansion node_u.assign_children(new Siblings(sib_u, u)); } dimension_ = dim_max - curr_dim - 1; - compute_punctual_expansion(v, - node_u.children(), - fil, - dim_max - curr_dim - 1, //>= 0 if dim_max >= 0, <0 otherwise - added_simplices); + compute_punctual_expansion( + v, + node_u.children(), + fil, + dim_max - curr_dim - 1, //>= 0 if dim_max >= 0, <0 otherwise + added_simplices ); dimension_ = dim_max - dimension_; if (dimension_ > tmp_max_dim) tmp_max_dim = dimension_; } } } - if (tmp_dim <= tmp_max_dim) { - dimension_ = tmp_max_dim; - dimension_to_be_lowered_ = false; + if (tmp_dim <= tmp_max_dim){ + dimension_ = tmp_max_dim; + dimension_to_be_lowered_ = false; } else { - dimension_ = tmp_dim; + dimension_ = tmp_dim; } } @@ -1650,36 +1938,44 @@ class Simplex_tree { * 2- All Node in the members of sib, with label @p x and @p x < @p v, * need in turn a local_expansion by @p v iff N^+(x) contains @p v. */ - void compute_punctual_expansion( - Vertex_handle v, - Siblings* sib, - const Filtration_value& fil, - int k // k == dim_max - dimension simplices in sib - , - std::vector& - added_simplices) { // insertion always succeeds because the edge {u,v} used to not be here. - auto res_ins_v = sib->members().emplace(v, Node(sib, fil)); - added_simplices.push_back(res_ins_v.first); // no more insertion in sib + void compute_punctual_expansion( Vertex_handle v + , Siblings * sib + , const Filtration_value& fil + , int k //k == dim_max - dimension simplices in sib + , std::vector& added_simplices ) + { //insertion always succeeds because the edge {u,v} used to not be here. + auto res_ins_v = sib->members().emplace(v, Node(sib,fil)); + added_simplices.push_back(res_ins_v.first); //no more insertion in sib update_simplex_tree_after_node_insertion(res_ins_v.first); - if (k == 0) { // reached the maximal dimension. if max_dim == -1, k is never equal to 0. + if (k == 0) { // reached the maximal dimension. if max_dim == -1, k is never equal to 0. dimension_ = 0; // to keep track of the max height of the recursion tree return; } - // create the subtree of new Node(v) - create_local_expansion(res_ins_v.first, sib, fil, k, added_simplices); - - // punctual expansion in nodes on the left of v, i.e. with label x < v - for (auto sh = sib->members().begin(); sh != res_ins_v.first; ++sh) { // if v belongs to N^+(x), punctual expansion - Simplex_handle root_sh = find_vertex(sh->first); // Node(x), x < v - if (has_children(root_sh) && root_sh->second.children()->members().find(v) != - root_sh->second.children()->members().end()) { // edge {x,v} is in the complex - if (!has_children(sh)) { + //create the subtree of new Node(v) + create_local_expansion( res_ins_v.first + , sib + , fil + , k + , added_simplices ); + + //punctual expansion in nodes on the left of v, i.e. with label x < v + for (auto sh = sib->members().begin(); sh != res_ins_v.first; ++sh) + { //if v belongs to N^+(x), punctual expansion + Simplex_handle root_sh = find_vertex(sh->first); //Node(x), x < v + if (has_children(root_sh) && + root_sh->second.children()->members().find(v) != root_sh->second.children()->members().end()) + { //edge {x,v} is in the complex + if (!has_children(sh)){ sh->second.assign_children(new Siblings(sib, sh->first)); } - // insert v in the children of sh, and expand. - compute_punctual_expansion(v, sh->second.children(), fil, k - 1, added_simplices); + //insert v in the children of sh, and expand. + compute_punctual_expansion( v + , sh->second.children() + , fil + , k-1 + , added_simplices ); } } } @@ -1690,28 +1986,24 @@ class Simplex_tree { * * k must be > 0 */ - void create_local_expansion(Simplex_handle sh_v // Node with label v which has just been inserted - , - Siblings* curr_sib // Siblings containing the node sh_v - , - const Filtration_value& fil_uv // Fil value of the edge uv in the zz filtration - , - int k // Stopping condition for recursion based on max dim - , - std::vector& added_simplices) // range of all new simplices - { // pick N^+(v) - // intersect N^+(v) with labels y > v in curr_sib - Simplex_handle next_it = sh_v; + void create_local_expansion( + Dictionary_it sh_v //Node with label v which has just been inserted + , Siblings *curr_sib //Siblings containing the node sh_v + , const Filtration_value& fil_uv //Fil value of the edge uv in the zz filtration + , int k //Stopping condition for recursion based on max dim + , std::vector &added_simplices) //range of all new simplices + { //pick N^+(v) + //intersect N^+(v) with labels y > v in curr_sib + Dictionary_it next_it = sh_v; ++next_it; if (dimension_ > k) { - dimension_ = k; // to keep track of the max height of the recursion tree + dimension_ = k; //to keep track of the max height of the recursion tree } create_expansion(curr_sib, sh_v, next_it, fil_uv, k, &added_simplices); } - - // TODO boost::container::ordered_unique_range_t in the creation of a Siblings + //TODO boost::container::ordered_unique_range_t in the creation of a Siblings /** \brief Global expansion of a subtree in the simplex tree. * @@ -1721,38 +2013,40 @@ class Simplex_tree { * * Only called in the case of `void insert_edge_as_flag(...)`. */ - void siblings_expansion(Siblings* siblings // must contain elements - , - const Filtration_value& fil, - int k // == max_dim expansion - dimension curr siblings - , - std::vector& added_simplices) { + void siblings_expansion( + Siblings * siblings // must contain elements + , const Filtration_value& fil + , int k // == max_dim expansion - dimension curr siblings + , std::vector & added_simplices ) + { if (dimension_ > k) { - dimension_ = k; // to keep track of the max height of the recursion tree + dimension_ = k; //to keep track of the max height of the recursion tree } - if (k == 0) { - return; - } // max dimension + if (k == 0) { return; } //max dimension Dictionary_it next = ++(siblings->members().begin()); - for (Dictionary_it s_h = siblings->members().begin(); next != siblings->members().end(); - ++s_h, ++next) { // find N^+(s_h) + for (Dictionary_it s_h = siblings->members().begin(); + next != siblings->members().end(); ++s_h, ++next) + { //find N^+(s_h) create_expansion(siblings, s_h, next, fil, k, &added_simplices); } } /** \brief Recursive expansion of the simplex tree. * Only called in the case of `void expansion(int max_dim)`. */ - void siblings_expansion(Siblings* siblings, // must contain elements + void siblings_expansion(Siblings * siblings, // must contain elements int k) { if (k >= 0 && dimension_ > k) { dimension_ = k; } - if (k == 0) return; + if (k == 0) + return; Dictionary_it next = siblings->members().begin(); ++next; - for (Dictionary_it s_h = siblings->members().begin(); s_h != siblings->members().end(); ++s_h, ++next) { + for (Dictionary_it s_h = siblings->members().begin(); + s_h != siblings->members().end(); ++s_h, ++next) + { create_expansion(siblings, s_h, next, s_h->second.filtration(), k); } } @@ -1761,38 +2055,40 @@ class Simplex_tree { * The method is used with `force_filtration_value == true` by `void insert_edge_as_flag(...)` and with * `force_filtration_value == false` by `void expansion(int max_dim)`. Therefore, `added_simplices` is assumed * to bon non-null in the first case and null in the second.*/ - template - void create_expansion(Siblings* siblings, + template + void create_expansion(Siblings * siblings, Dictionary_it& s_h, Dictionary_it& next, const Filtration_value& fil, int k, - std::vector* added_simplices = nullptr) { + std::vector* added_simplices = nullptr) + { Simplex_handle root_sh = find_vertex(s_h->first); - thread_local std::vector> inter; + thread_local std::vector > inter; if (!has_children(root_sh)) return; - intersection(inter, // output intersection - next, // begin - siblings->members().end(), // end - root_sh->second.children()->members().begin(), - root_sh->second.children()->members().end(), - fil); + intersection( + inter, // output intersection + next, // begin + siblings->members().end(), // end + root_sh->second.children()->members().begin(), + root_sh->second.children()->members().end(), + fil); if (inter.size() != 0) { - Siblings* new_sib = new Siblings(siblings, // oncles - s_h->first, // parent - inter); // boost::container::ordered_unique_range_t + Siblings * new_sib = new Siblings(siblings, // oncles + s_h->first, // parent + inter); // boost::container::ordered_unique_range_t for (auto it = new_sib->members().begin(); it != new_sib->members().end(); ++it) { update_simplex_tree_after_node_insertion(it); - if constexpr (force_filtration_value) { - // the way create_expansion is used, added_simplices != nullptr when force_filtration_value == true + if constexpr (force_filtration_value){ + //the way create_expansion is used, added_simplices != nullptr when force_filtration_value == true added_simplices->push_back(it); } } inter.clear(); s_h->second.assign_children(new_sib); - if constexpr (force_filtration_value) { + if constexpr (force_filtration_value){ siblings_expansion(new_sib, fil, k - 1, *added_simplices); } else { siblings_expansion(new_sib, k - 1); @@ -1806,17 +2102,16 @@ class Simplex_tree { /** \brief Intersects Dictionary 1 [begin1;end1) with Dictionary 2 [begin2,end2) * and assigns the maximal possible Filtration_value to the Nodes. */ - template - static void intersection(std::vector>& intersection, - Dictionary_it begin1, - Dictionary_it end1, - Dictionary_it begin2, - Dictionary_it end2, + template + static void intersection(std::vector >& intersection, + Dictionary_const_it begin1, Dictionary_const_it end1, + Dictionary_const_it begin2, Dictionary_const_it end2, const Filtration_value& filtration_) { - if (begin1 == end1 || begin2 == end2) return; // ----->> + if (begin1 == end1 || begin2 == end2) + return; // ----->> while (true) { if (begin1->first == begin2->first) { - if constexpr (force_filtration_value) { + if constexpr (force_filtration_value){ intersection.emplace_back(begin1->first, Node(nullptr, filtration_)); } else { Filtration_value filt = begin1->second.filtration(); @@ -1824,11 +2119,14 @@ class Simplex_tree { intersect_lifetimes(filt, filtration_); intersection.emplace_back(begin1->first, Node(nullptr, filt)); } - if (++begin1 == end1 || ++begin2 == end2) return; // ----->> + if (++begin1 == end1 || ++begin2 == end2) + return; // ----->> } else if (begin1->first < begin2->first) { - if (++begin1 == end1) return; + if (++begin1 == end1) + return; } else /* begin1->first > begin2->first */ { - if (++begin2 == end2) return; // ----->> + if (++begin2 == end2) + return; // ----->> } } } @@ -1852,7 +2150,7 @@ class Simplex_tree { * so if you examine the complex in `block_simplex`, you may hit a few simplices of the same dimension that have not * been vetted by `block_simplex` yet, or have already been rejected but not yet removed. */ - template + template< typename Blocker > void expansion_with_blockers(int max_dim, Blocker block_simplex) { // Loop must be from the end to the beginning, as higher dimension simplex are always on the left part of the tree for (auto& simplex : boost::adaptors::reverse(root_.members())) { @@ -1864,25 +2162,27 @@ class Simplex_tree { private: /** \brief Recursive expansion with blockers of the simplex tree.*/ - template + template< typename Blocker > void siblings_expansion_with_blockers(Siblings* siblings, int max_dim, int k, Blocker block_simplex) { if (dimension_ < max_dim - k) { dimension_ = max_dim - k; } - if (k == 0) return; + if (k == 0) + return; // No need to go deeper - if (siblings->members().size() < 2) return; + if (siblings->members().size() < 2) + return; // Reverse loop starting before the last one for 'next' to be the last one for (auto simplex = std::next(siblings->members().rbegin()); simplex != siblings->members().rend(); simplex++) { - std::vector> intersection; - for (auto next = siblings->members().rbegin(); next != simplex; next++) { + std::vector > intersection; + for(auto next = siblings->members().rbegin(); next != simplex; next++) { bool to_be_inserted = true; Filtration_value filt = simplex->second.filtration(); // If all the boundaries are present, 'next' needs to be inserted for (Simplex_handle border : boundary_simplex_range(simplex)) { Simplex_handle border_child = find_child(border, next->first); if (border_child == null_simplex()) { - to_be_inserted = false; + to_be_inserted=false; break; } intersect_lifetimes(filt, filtration(border_child)); @@ -1893,25 +2193,26 @@ class Simplex_tree { } if (intersection.size() != 0) { // Reverse the order to insert - Siblings* new_sib = - new Siblings(siblings, // oncles - simplex->first, // parent - boost::adaptors::reverse(intersection)); // boost::container::ordered_unique_range_t + Siblings * new_sib = new Siblings( + siblings, // oncles + simplex->first, // parent + boost::adaptors::reverse(intersection)); // boost::container::ordered_unique_range_t simplex->second.assign_children(new_sib); std::vector blocked_new_sib_vertex_list; // As all intersections are inserted, we can call the blocker function on all new_sib members - for (auto new_sib_member = new_sib->members().begin(); new_sib_member != new_sib->members().end(); + for (auto new_sib_member = new_sib->members().begin(); + new_sib_member != new_sib->members().end(); new_sib_member++) { - update_simplex_tree_after_node_insertion(new_sib_member); - bool blocker_result = block_simplex(new_sib_member); - // new_sib member has been blocked by the blocker function - // add it to the list to be removed - do not perform it while looping on it - if (blocker_result) { - blocked_new_sib_vertex_list.push_back(new_sib_member->first); - // update data structures for all deleted simplices - // can be done in the loop as part of another data structure - update_simplex_tree_before_node_removal(new_sib_member); - } + update_simplex_tree_after_node_insertion(new_sib_member); + bool blocker_result = block_simplex(new_sib_member); + // new_sib member has been blocked by the blocker function + // add it to the list to be removed - do not perform it while looping on it + if (blocker_result) { + blocked_new_sib_vertex_list.push_back(new_sib_member->first); + // update data structures for all deleted simplices + // can be done in the loop as part of another data structure + update_simplex_tree_before_node_removal(new_sib_member); + } } if (blocked_new_sib_vertex_list.size() == new_sib->members().size()) { // Specific case where all have to be deleted @@ -1935,14 +2236,16 @@ class Simplex_tree { /** \private Returns the Simplex_handle composed of the vertex list (from the Simplex_handle), plus the given * Vertex_handle if the Vertex_handle is found in the Simplex_handle children list. * Returns null_simplex() if it does not exist - */ + */ Simplex_handle find_child(Simplex_handle sh, Vertex_handle vh) const { - if (!has_children(sh)) return null_simplex(); + if (!has_children(sh)) + return null_simplex(); Simplex_handle child = sh->second.children()->find(vh); // Specific case of boost::flat_map does not find, returns boost::flat_map::end() // in simplex tree we want a null_simplex() - if (child == sh->second.children()->members().end()) return null_simplex(); + if (child == sh->second.children()->members().end()) + return null_simplex(); return child; } @@ -1953,9 +2256,13 @@ class Simplex_tree { * Each row in the file correspond to a simplex. A line is written: * dim idx_1 ... idx_k fil where dim is the dimension of the simplex, * idx_1 ... idx_k are the row index (starting from 0) of the simplices of the boundary - * of the simplex, and fil is its filtration value. */ - void print_hasse(std::ostream& os) { + * of the simplex, and fil is its filtration value. + * + * @note Not thread safe + */ + void print_hasse(std::ostream& os) const { os << num_simplices() << " " << std::endl; + // TODO: should use complex_simplex_range ? for (auto sh : filtration_simplex_range()) { os << dimension(sh) << " "; for (auto b_sh : boundary_simplex_range(sh)) { @@ -1974,8 +2281,8 @@ class Simplex_tree { * simplex). It may return void or bool, and in the second case returning true means that the iteration will skip * the children of this simplex (a subset of the cofaces). */ - template - void for_each_simplex(Fun&& fun) { + template + void for_each_simplex(Fun&& fun) const { // Wrap callback so it always returns bool auto f = [&fun](Simplex_handle sh, int dim) -> bool { if constexpr (std::is_same_v) { @@ -1985,21 +2292,23 @@ class Simplex_tree { return fun(sh, dim); } }; - if (!is_empty()) rec_for_each_simplex(root(), 0, f); + if (!is_empty()) + rec_for_each_simplex(root(), 0, f); } private: - template - void rec_for_each_simplex(Siblings* sib, int dim, Fun&& fun) { + template + void rec_for_each_simplex(const Siblings* sib, int dim, Fun&& fun) const { Simplex_handle sh = sib->members().end(); GUDHI_CHECK(sh != sib->members().begin(), "Bug in Gudhi: only the root siblings may be empty"); do { --sh; if (!fun(sh, dim) && has_children(sh)) { - rec_for_each_simplex(sh->second.children(), dim + 1, fun); + rec_for_each_simplex(sh->second.children(), dim+1, fun); } // We could skip checking has_children for the first element of the iteration, we know it returns false. - } while (sh != sib->members().begin()); + } + while(sh != sib->members().begin()); } public: @@ -2014,7 +2323,7 @@ class Simplex_tree { auto fun = [&modified, this](Simplex_handle sh, int dim) -> void { if (dim == 0) return; - Filtration_value& current_filt = sh->second.filtration(); + Filtration_value& current_filt = _to_node_it(sh)->second.filtration(); // Find the maximum filtration value in the border and assigns it if it is greater than the current for (Simplex_handle b : boundary_simplex_range(sh)) { @@ -2026,7 +2335,8 @@ class Simplex_tree { // Loop must be from the end to the beginning, as higher dimension simplex are always on the left part of the tree for_each_simplex(fun); - if (modified) clear_filtration(); // Drop the cache. + if (modified) + clear_filtration(); // Drop the cache. return modified; } @@ -2037,7 +2347,8 @@ class Simplex_tree { clear_filtration(); dimension_ = -1; dimension_to_be_lowered_ = false; - if constexpr (Options::link_nodes_by_label) nodes_label_to_list_.clear(); + if constexpr (Options::link_nodes_by_label) + nodes_label_to_list_.clear(); } /** \brief Prune above filtration value given as parameter. That is: if \f$ f \f$ is the given filtration value @@ -2052,22 +2363,15 @@ class Simplex_tree { * bound. If you care, you can call `dimension()` to recompute the exact dimension. */ bool prune_above_filtration(const Filtration_value& filtration) { - if (filtration == Filtration_simplex_base_real::get_infinity()) return false; // ---->> + if (is_positive_infinity(filtration)) + return false; // ---->> bool modified = rec_prune_above_filtration(root(), filtration); - if (modified) clear_filtration(); // Drop the cache. + if(modified) + clear_filtration(); // Drop the cache. return modified; } private: - void make_subfiltration_non_decreasing(Simplex_handle sh) { - static_assert(!Filtration_value::is_multicritical(), "Not implemented yet."); - Filtration_value& stuff = filtration_mutable(sh); - for (auto sh_gosse : boundary_simplex_range(sh)) { - make_subfiltration_non_decreasing(sh_gosse); - intersect_lifetimes(stuff, filtration(sh_gosse)); - } - } - bool rec_prune_above_filtration(Siblings* sib, const Filtration_value& filt) { auto&& list = sib->members(); bool modified = false; @@ -2075,8 +2379,8 @@ class Simplex_tree { Simplex_handle last; auto to_remove = [this, filt](Dit_value_t& simplex) { - // if filt and simplex.second.filtration() are non comparable, should return false. - // if simplex.second.filtration() is NaN, should return true. + //if filt and simplex.second.filtration() are non comparable, should return false. + //if simplex.second.filtration() is NaN, should return true. if (filt < simplex.second.filtration() || !(simplex.second.filtration() == simplex.second.filtration())) { if (has_children(&simplex)) rec_delete(simplex.second.children()); // dimension may need to be lowered @@ -2086,8 +2390,8 @@ class Simplex_tree { return false; }; - // TODO: `if constexpr` replaceable by `std::erase_if` in C++20? Has a risk of additional runtime, - // so to benchmark first. + //TODO: `if constexpr` replaceable by `std::erase_if` in C++20? Has a risk of additional runtime, + //so to benchmark first. if constexpr (Options::stable_simplex_handles) { modified = false; for (auto sh = list.begin(); sh != list.end();) { @@ -2128,7 +2432,8 @@ class Simplex_tree { * @return True if any simplex was removed, false if all simplices already had a value below the dimension. */ bool prune_above_dimension(int dimension) { - if (dimension >= dimension_) return false; + if (dimension >= dimension_) + return false; bool modified = false; if (dimension < 0) { @@ -2141,11 +2446,10 @@ class Simplex_tree { } else { modified = rec_prune_above_dimension(root(), dimension, 0); } - if (modified) { - // Thanks to `if (dimension >= dimension_)` and dimension forced to -1 `if (dimension < 0)`, we know the new - // dimension + if(modified) { + // Thanks to `if (dimension >= dimension_)` and dimension forced to -1 `if (dimension < 0)`, we know the new dimension dimension_ = dimension; - clear_filtration(); // Drop the cache. + clear_filtration(); // Drop the cache. } return modified; } @@ -2175,7 +2479,7 @@ class Simplex_tree { * \pre Be sure the simplex tree has not a too low dimension value as the deep search stops when the former dimension * has been reached (cf. `upper_bound_dimension()` and `set_dimension()` methods). */ - bool lower_upper_bound_dimension() { + bool lower_upper_bound_dimension() const { // reset automatic detection to recompute dimension_to_be_lowered_ = false; int new_dimension = -1; @@ -2198,6 +2502,7 @@ class Simplex_tree { return true; } + public: /** \brief Remove a maximal simplex. * @param[in] sh Simplex handle on the maximal simplex to remove. @@ -2215,12 +2520,13 @@ class Simplex_tree { update_simplex_tree_before_node_removal(sh); // Simplex is a leaf, it means the child is the Siblings owning the leaf - Siblings* child = sh->second.children(); + Dictionary_it nodeIt = _to_node_it(sh); + Siblings* child = nodeIt->second.children(); if ((child->size() > 1) || (child == root())) { // Not alone, just remove it from members // Special case when child is the root of the simplex tree, just remove it from members - child->erase(sh); + child->erase(nodeIt); } else { // Sibling is emptied : must be deleted, and its parent must point on his own Sibling child->oncles()->members().at(child->parent()).assign_children(child->oncles()); @@ -2236,20 +2542,20 @@ class Simplex_tree { * in the Simplex_tree. Hence, this function also outputs the type of each simplex. It can be either UP (which means * that the simplex was present originally, and is thus part of the ascending extended filtration), DOWN (which means * that the simplex is the cone of an original simplex, and is thus part of the descending extended filtration) or - * EXTRA (which means the simplex is the cone point). See the definition of Extended_simplex_type. Note that if the - * simplex type is DOWN, the original filtration value is set to be the original filtration value of the corresponding - * (not coned) original simplex. + * EXTRA (which means the simplex is the cone point). See the definition of Extended_simplex_type. Note that if the simplex type is DOWN, the original filtration value + * is set to be the original filtration value of the corresponding (not coned) original simplex. * \pre This function should be called only if `extend_filtration()` has been called first! * \post The output filtration value is supposed to be the same, but might be a little different, than the * original filtration value, due to the internal transformation (scaling to [-2,-1]) that is * performed on the original filtration values during the computation of extended persistence. * @param[in] f Filtration value of the simplex in the extended (i.e., modified) filtration. - * @param[in] efd Structure containing the minimum and maximum values of the original filtration. This the output of - * `extend_filtration()`. + * @param[in] efd Structure containing the minimum and maximum values of the original filtration. This the output of `extend_filtration()`. * @return A pair containing the original filtration value of the simplex as well as the simplex type. */ - std::pair decode_extended_filtration(const Filtration_value& f, - const Extended_filtration_data& efd) { + std::pair decode_extended_filtration( + const Filtration_value& f, + const Extended_filtration_data& efd) const + { std::pair p; const Filtration_value& minval = efd.minval; const Filtration_value& maxval = efd.maxval; @@ -2266,7 +2572,7 @@ class Simplex_tree { return p; }; - // TODO: externalize this method and `decode_extended_filtration` + //TODO: externalize this method and `decode_extended_filtration` /** \brief Extend filtration for computing extended persistence. * This function only uses the filtration values at the 0-dimensional simplices, * and computes the extended persistence diagram induced by the lower-star filtration @@ -2285,12 +2591,12 @@ class Simplex_tree { * the original filtration values for each simplex. */ Extended_filtration_data extend_filtration() { - clear_filtration(); // Drop the cache. + clear_filtration(); // Drop the cache. // Compute maximum and minimum of filtration values Vertex_handle maxvert = std::numeric_limits::min(); Filtration_value minval = Filtration_simplex_base_real::get_infinity(); - Filtration_value maxval = -Filtration_simplex_base_real::get_infinity(); + Filtration_value maxval = Filtration_simplex_base_real::get_minus_infinity(); for (auto sh = root_.members().begin(); sh != root_.members().end(); ++sh) { const Filtration_value& f = this->filtration(sh); minval = std::min(minval, f); @@ -2298,8 +2604,7 @@ class Simplex_tree { maxvert = std::max(sh->first, maxvert); } - GUDHI_CHECK(maxvert < std::numeric_limits::max(), - std::invalid_argument("Simplex_tree contains a vertex with the largest Vertex_handle")); + GUDHI_CHECK(maxvert < std::numeric_limits::max(), std::invalid_argument("Simplex_tree contains a vertex with the largest Vertex_handle")); maxvert++; Simplex_tree st_copy = *this; @@ -2307,8 +2612,9 @@ class Simplex_tree { // Add point for coning the simplicial complex this->insert_simplex_raw({maxvert}, -3); - Filtration_value scale = maxval - minval; - if (scale != 0) scale = 1 / scale; + Filtration_value scale = maxval-minval; + if (scale != 0) + scale = 1 / scale; // For each simplex std::vector vr; @@ -2340,80 +2646,76 @@ class Simplex_tree { return Extended_filtration_data(minval, maxval); } - /** \brief Returns a vertex of `sh` that has the same filtration value as `sh` if it exists, and `null_vertex()` - * otherwise. + /** \brief Returns a vertex of `sh` that has the same filtration value as `sh` if it exists, and `null_vertex()` otherwise. * - * For a lower-star filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and - * find out which vertex had its filtration value propagated to `sh`. If several vertices have the same filtration - * value, the one it returns is arbitrary. */ - Vertex_handle vertex_with_same_filtration(Simplex_handle sh) { + * For a lower-star filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which vertex had its filtration value propagated to `sh`. + * If several vertices have the same filtration value, the one it returns is arbitrary. */ + Vertex_handle vertex_with_same_filtration(Simplex_handle sh) const { auto filt = filtration_(sh); - for (auto v : simplex_vertex_range(sh)) - if (filtration_(find_vertex(v)) == filt) return v; + for(auto v : simplex_vertex_range(sh)) + if(filtration_(find_vertex(v)) == filt) + return v; return null_vertex(); } - /** \brief Returns an edge of `sh` that has the same filtration value as `sh` if it exists, and `null_simplex()` - * otherwise. + /** \brief Returns an edge of `sh` that has the same filtration value as `sh` if it exists, and `null_simplex()` otherwise. * - * For a flag-complex built with `expansion()`, this is a way to invert the process and find out which edge had its - * filtration value propagated to `sh`. If several edges have the same filtration value, the one it returns is - * arbitrary. + * For a flag-complex built with `expansion()`, this is a way to invert the process and find out which edge had its filtration value propagated to `sh`. + * If several edges have the same filtration value, the one it returns is arbitrary. * * \pre `sh` must have dimension at least 1. */ - Simplex_handle edge_with_same_filtration(Simplex_handle sh) { + Simplex_handle edge_with_same_filtration(Simplex_handle sh) const { // See issue #251 for potential speed improvements. - auto&& vertices = simplex_vertex_range(sh); // vertices in decreasing order + auto&& vertices = simplex_vertex_range(sh); // vertices in decreasing order auto end = std::end(vertices); auto vi = std::begin(vertices); GUDHI_CHECK(vi != end, "empty simplex"); auto v0 = *vi; ++vi; GUDHI_CHECK(vi != end, "simplex of dimension 0"); - if (std::next(vi) == end) return sh; // shortcut for dimension 1 + if(std::next(vi) == end) return sh; // shortcut for dimension 1 Static_vertex_vector suffix; suffix.push_back(v0); auto filt = filtration_(sh); - do { + do + { Vertex_handle v = *vi; auto&& children1 = find_vertex(v)->second.children()->members_; - for (auto w : suffix) { + for(auto w : suffix){ // Can we take advantage of the fact that suffix is ordered? Simplex_handle s = children1.find(w); - if (filtration_(s) == filt) return s; + if(filtration_(s) == filt) + return s; } suffix.push_back(v); - } while (++vi != end); + } + while(++vi != end); return null_simplex(); } /** \brief Returns a minimal face of `sh` that has the same filtration value as `sh`. * - * For a filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out - * which simplex had its filtration value propagated to `sh`. If several minimal (for inclusion) simplices have the - * same filtration value, the one it returns is arbitrary, and it is not guaranteed to be the one with smallest - * dimension. */ - Simplex_handle minimal_simplex_with_same_filtration(Simplex_handle sh) { + * For a filtration built with `make_filtration_non_decreasing()`, this is a way to invert the process and find out which simplex had its filtration value propagated to `sh`. + * If several minimal (for inclusion) simplices have the same filtration value, the one it returns is arbitrary, and it is not guaranteed to be the one with smallest dimension. */ + Simplex_handle minimal_simplex_with_same_filtration(Simplex_handle sh) const { auto filt = filtration_(sh); // Naive implementation, it can be sped up. - for (auto b : boundary_simplex_range(sh)) - if (filtration_(b) == filt) return minimal_simplex_with_same_filtration(b); - return sh; // None of its faces has the same filtration. + for(auto b : boundary_simplex_range(sh)) + if(filtration_(b) == filt) + return minimal_simplex_with_same_filtration(b); + return sh; // None of its faces has the same filtration. } public: // intrusive list of Nodes with same label using the hooks - typedef boost::intrusive::member_hook List_member_hook_t; // auto_unlink in Member_hook_t is incompatible with constant time size - typedef boost::intrusive:: - list> - List_max_vertex; + typedef boost::intrusive::list> List_max_vertex; // type of hooks stored in each Node, Node inherits from Hooks_simplex_base - typedef typename std::conditional::type Hooks_simplex_base; /** Data structure to access all Nodes with a given label u. Can be used for faster @@ -2424,6 +2726,12 @@ class Simplex_tree { std::unordered_map nodes_label_to_list_; List_max_vertex* nodes_by_label(Vertex_handle v) { + // Scott Meyers in Effective C++ 3rd Edition. On page 23, Item 3: a non const method can safely call a const one + // Doing it the other way is not safe + return const_cast(std::as_const(*this).nodes_by_label(v)); + } + + List_max_vertex const* nodes_by_label(Vertex_handle v) const { if constexpr (Options::link_nodes_by_label) { auto it_v = nodes_label_to_list_.find(v); if (it_v != nodes_label_to_list_.end()) { @@ -2437,45 +2745,47 @@ class Simplex_tree { /** \brief Helper method that returns the corresponding Simplex_handle from a member element defined by a node. */ - static Simplex_handle simplex_handle_from_node(Node& node) { - if constexpr (Options::stable_simplex_handles) { - // Relies on the Dictionary type to be boost::container::map. - // If the type changes or boost fundamentally changes something on the structure of their map, - // a safer/more general but much slower version is: - // if (node.children()->parent() == label) { // verifies if node is a leaf - // return children->oncles()->find(label); - // } else { - // return children->members().find(label); - // } - // Requires an additional parameter "Vertex_handle label" which is the label of the node. - - Dictionary_it testIt = node.children()->members().begin(); - Node* testNode = &testIt->second; + static Simplex_handle simplex_handle_from_node(const Node& node) { + if constexpr (Options::stable_simplex_handles){ + //Relies on the Dictionary type to be boost::container::map. + //If the type changes or boost fundamentally changes something on the structure of their map, + //a safer/more general but much slower version is: + // if (node.children()->parent() == label) { // verifies if node is a leaf + // return children->oncles()->find(label); + // } else { + // return children->members().find(label); + // } + //Requires an additional parameter "Vertex_handle label" which is the label of the node. + + Dictionary_const_it testIt = node.children()->members().begin(); + const Node* testNode = &testIt->second; auto testIIt = testIt.get(); auto testPtr = testIIt.pointed_node(); - // distance between node and pointer to map pair in memory + //distance between node and pointer to map pair in memory auto shift = (const char*)(testNode) - (const char*)(testPtr); - // decltype(testPtr) = boost::intrusive::compact_rbtree_node* - decltype(testPtr) sh_ptr = decltype(testPtr)((const char*)(&node) - shift); // shifts from node to pointer - // decltype(testIIt) = - // boost::intrusive::tree_iterator< - // boost::intrusive::bhtraits< - // boost::container::base_node< - // std::pair>, - // boost::container::dtl::intrusive_tree_hook, true>, - // boost::intrusive::rbtree_node_traits, - // boost::intrusive::normal_link, - // boost::intrusive::dft_tag, - // 3>, - // false> + //decltype(testPtr) = boost::intrusive::compact_rbtree_node* + decltype(testPtr) sh_ptr = decltype(testPtr)((const char*)(&node) - shift); //shifts from node to pointer + //decltype(testIIt) = + //boost::intrusive::tree_iterator< + // boost::intrusive::bhtraits< + // boost::container::base_node< + // std::pair>, + // boost::container::dtl::intrusive_tree_hook, true>, + // boost::intrusive::rbtree_node_traits, + // boost::intrusive::normal_link, + // boost::intrusive::dft_tag, + // 3>, + // false> decltype(testIIt) sh_ii; - sh_ii = sh_ptr; // creates ``subiterator'' from pointer - Dictionary_it sh(sh_ii); // creates iterator from subiterator + sh_ii = sh_ptr; //creates ``subiterator'' from pointer + Dictionary_const_it sh(sh_ii); //creates iterator from subiterator return sh; } else { - return (Simplex_handle)(boost::intrusive::get_parent_from_member(&node, &Dit_value_t::second)); + //node needs to be casted as non const, because a const pair* cannot be casted into a Simplex_handle + return (Simplex_handle)(boost::intrusive::get_parent_from_member(const_cast(&node), + &Dit_value_t::second)); } } @@ -2492,7 +2802,7 @@ class Simplex_tree { #endif // DEBUG_TRACES if constexpr (Options::link_nodes_by_label) { // Creates an entry with sh->first if not already in the map and insert sh->second at the end of the list - nodes_label_to_list_[sh->first].push_back(sh->second); + nodes_label_to_list_[sh->first].push_back(_to_node_it(sh)->second); } } @@ -2503,8 +2813,9 @@ class Simplex_tree { std::clog << "update_simplex_tree_before_node_removal" << std::endl; #endif // DEBUG_TRACES if constexpr (Options::link_nodes_by_label) { - sh->second.unlink_hooks(); // remove from lists of same label Nodes - if (nodes_label_to_list_[sh->first].empty()) nodes_label_to_list_.erase(sh->first); + _to_node_it(sh)->second.unlink_hooks(); // remove from lists of same label Nodes + if (nodes_label_to_list_[sh->first].empty()) + nodes_label_to_list_.erase(sh->first); } } @@ -2512,14 +2823,14 @@ class Simplex_tree { /** \brief This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all * the Simplex_tree when `min_dim = 0`. * `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to - * make it a valid filtration (using a large enough `filt_value`, or calling `make_filtration_non_decreasing` + * make it a valid filtration (using a large enough `filtration`, or calling `make_filtration_non_decreasing` * afterwards for instance). - * @param[in] filt_value The new filtration value. + * @param[in] filtration The new filtration value. * @param[in] min_dim The minimal dimension. Default value is 0. */ - void reset_filtration(const Filtration_value& filt_value, int min_dim = 0) { - rec_reset_filtration(&root_, filt_value, min_dim); - clear_filtration(); // Drop the cache. + void reset_filtration(const Filtration_value& filtration, int min_dim = 0) { + rec_reset_filtration(&root_, filtration, min_dim); + clear_filtration(); // Drop the cache. } private: @@ -2528,7 +2839,7 @@ class Simplex_tree { * @param[in] filt_value The new filtration value. * @param[in] min_depth The minimal depth. */ - void rec_reset_filtration(Siblings* sib, const Filtration_value& filt_value, int min_depth) { + void rec_reset_filtration(Siblings * sib, const Filtration_value& filt_value, int min_depth) { for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { if (min_depth <= 0) { sh->second.assign_filtration(filt_value); @@ -2539,7 +2850,7 @@ class Simplex_tree { } } - std::size_t num_simplices_and_filtration_size(Siblings* sib, std::size_t& fv_byte_size) const { + std::size_t num_simplices_and_filtration_serialization_size(Siblings const* sib, std::size_t& fv_byte_size) const { using namespace Gudhi::simplex_tree; auto sib_begin = sib->members().begin(); @@ -2549,36 +2860,39 @@ class Simplex_tree { if constexpr (SimplexTreeOptions::store_filtration) fv_byte_size += get_serialization_size_of(sh->second.filtration()); if (has_children(sh)) { - simplices_number += num_simplices_and_filtration_size(sh->second.children(), fv_byte_size); + simplices_number += num_simplices_and_filtration_serialization_size(sh->second.children(), fv_byte_size); } } return simplices_number; } public: - /** @private @brief Returns the serialization required buffer size. + /** + * @private + * @brief Returns the serialization required buffer size. * * @return The exact serialization required size in number of bytes. * * @warning It is meant to return the same size with the same SimplexTreeOptions and on a computer with the same * architecture. */ - std::size_t get_serialization_size() { - using namespace Gudhi::simplex_tree; - - const std::size_t num_param_byte_size = sizeof(decltype(number_of_parameters_)); + std::size_t get_serialization_size() const { + // commented for the time the released gudhi version adds number_of_parameters_ + // multipers uses it to convert a gudhi.SimplexTree to a multipers.SimplexTree + const std::size_t np_byte_size = 0; //sizeof(decltype(number_of_parameters_)); const std::size_t vh_byte_size = sizeof(Vertex_handle); std::size_t fv_byte_size = 0; - const std::size_t tree_size = num_simplices_and_filtration_size(&root_, fv_byte_size); - const std::size_t buffer_byte_size = - num_param_byte_size + vh_byte_size + fv_byte_size + tree_size * 2 * vh_byte_size; + const std::size_t tree_size = num_simplices_and_filtration_serialization_size(&root_, fv_byte_size); + const std::size_t buffer_byte_size = np_byte_size + vh_byte_size + fv_byte_size + tree_size * 2 * vh_byte_size; #ifdef DEBUG_TRACES - std::clog << "Gudhi::simplex_tree::get_serialization_size - buffer size = " << buffer_byte_size << std::endl; + std::clog << "Gudhi::simplex_tree::get_serialization_size - buffer size = " << buffer_byte_size << std::endl; #endif // DEBUG_TRACES return buffer_byte_size; } - /** @private @brief Serialize the Simplex tree - Flatten it in a user given array of char + /** + * @private + * @brief Serialize the Simplex tree - Flatten it in a user given array of char * * @param[in] buffer An array of char allocated with enough space (cf. Gudhi::simplex_tree::get_serialization_size) * @param[in] buffer_size The buffer size. @@ -2587,6 +2901,8 @@ class Simplex_tree { * * @warning Serialize/Deserialize is not portable. It is meant to be read in a Simplex_tree with the same * SimplexTreeOptions and on a computer with the same architecture. + * + * Serialize/Deserialize ignores any extra data (@ref Simplex_data) stored in the simplices for now. */ /* Let's take the following simplicial complex as example: */ /* (vertices are represented as letters to ease the understanding) */ @@ -2606,13 +2922,12 @@ class Simplex_tree { /* 0d(list of [b,c] children)00(number of [b,c,d] children)00(number of [b,d] children)01(number of [c] children) */ /* 0d(list of [c] children)00(number of [b,d] children)00(number of [d] children) */ /* Without explanation and with filtration values: */ - /* 04 0a F(a) 0b F(b) 0c F(c) 0d F(d) 01 0b F(a,b) 00 02 0c F(b,c) 0d F(b,d) 01 0d F(b,c,d) 00 00 01 0d F(c,d) 00 00 - */ - void serialize(char* buffer, const std::size_t buffer_size) { + /* 04 0a F(a) 0b F(b) 0c F(c) 0d F(d) 01 0b F(a,b) 00 02 0c F(b,c) 0d F(b,d) 01 0d F(b,c,d) 00 00 01 0d F(c,d) 00 00 */ + void serialize(char* buffer, const std::size_t buffer_size) const { char* buffer_end = buffer; - if constexpr (Options::is_multi_parameter) { - buffer_end = simplex_tree::serialize_trivial(number_of_parameters_, buffer_end); - } + // commented for the time the released gudhi version adds number_of_parameters_ + // multipers uses it to convert a gudhi.SimplexTree to a multipers.SimplexTree + // buffer_end = serialize_value_to_char_buffer(number_of_parameters_, buffer_end); buffer_end = rec_serialize(&root_, buffer_end); if (static_cast(buffer_end - buffer) != buffer_size) throw std::invalid_argument("Serialization does not match end of buffer"); @@ -2620,16 +2935,16 @@ class Simplex_tree { private: /** \brief Serialize each element of the sibling and recursively call serialization. */ - char* rec_serialize(Siblings* sib, char* buffer) { - using namespace Gudhi::simplex_tree; + char* rec_serialize(Siblings const *sib, char* buffer) const { char* ptr = buffer; - ptr = serialize_trivial(static_cast(sib->members().size()), ptr); + ptr = serialize_value_to_char_buffer(static_cast(sib->members().size()), ptr); #ifdef DEBUG_TRACES std::clog << "\n" << sib->members().size() << " : "; #endif // DEBUG_TRACES for (auto& map_el : sib->members()) { - ptr = serialize_trivial(map_el.first, ptr); // Vertex - if (Options::store_filtration) ptr = serialize_trivial(map_el.second.filtration(), ptr); // Filtration + ptr = serialize_value_to_char_buffer(map_el.first, ptr); // Vertex + if (Options::store_filtration) + ptr = serialize_value_to_char_buffer(map_el.second.filtration(), ptr); // Filtration #ifdef DEBUG_TRACES std::clog << " [ " << map_el.first << " | " << map_el.second.filtration() << " ] "; #endif // DEBUG_TRACES @@ -2638,7 +2953,7 @@ class Simplex_tree { if (has_children(&map_el)) { ptr = rec_serialize(map_el.second.children(), ptr); } else { - ptr = serialize_trivial(static_cast(0), ptr); + ptr = serialize_value_to_char_buffer(static_cast(0), ptr); #ifdef DEBUG_TRACES std::cout << "\n0 : "; #endif // DEBUG_TRACES @@ -2648,7 +2963,9 @@ class Simplex_tree { } public: - /** @private @brief Deserialize the array of char (flatten version of the tree) to initialize a Simplex tree. + /** + * @private + * @brief Deserialize the array of char (flatten version of the tree) to initialize a Simplex tree. * It is the user's responsibility to provide an 'empty' Simplex_tree, there is no guarantee otherwise. * * @param[in] buffer A pointer on a buffer that contains a serialized Simplex_tree. @@ -2660,20 +2977,48 @@ class Simplex_tree { * @warning Serialize/Deserialize is not portable. It is meant to be read in a Simplex_tree with the same * SimplexTreeOptions and on a computer with the same architecture. * + * Serialize/Deserialize ignores any extra data (@ref Simplex_data) stored in the simplices for now. */ void deserialize(const char* buffer, const std::size_t buffer_size) { - using namespace Gudhi::simplex_tree; + deserialize(buffer, buffer_size, [](Filtration_value& filtration, const char* ptr) { + return deserialize_value_from_char_buffer(filtration, ptr); + }); + } + + /** + * @private + * @brief Deserialize the array of char (flattened version of the tree) to initialize a Simplex tree. + * It is the user's responsibility to provide an 'empty' Simplex_tree, there is no guarantee otherwise. + * + * @tparam F Method taking a reference to a @ref Filtration_value and a `const char*` as input and returning a + * `const char*`. + * @param[in] buffer A pointer on a buffer that contains a serialized Simplex_tree. + * @param[in] buffer_size The size of the buffer. + * @param[in] deserialize_filtration_value To provide if the type of @ref Filtration_value does not correspond to + * the filtration value type of the serialized simplex tree. The method should be able to read from a buffer + * (second argument) the serialized filtration value and turn it into an object of type @ref Filtration_value that is + * stored in the first argument of the method. It then returns the new position of the buffer pointer after the + * reading. + * + * @exception std::invalid_argument In case the deserialization does not finish at the correct buffer_size. + * @exception std::logic_error In debug mode, if the Simplex_tree is not 'empty'. + * + * @warning Serialize/Deserialize is not portable. It is meant to be read in a Simplex_tree with the same + * SimplexTreeOptions (except for the @ref Filtration_value type) and on a computer with the same architecture. + * + */ + template + void deserialize(const char* buffer, const std::size_t buffer_size, F&& deserialize_filtration_value) { GUDHI_CHECK(num_vertices() == 0, std::logic_error("Simplex_tree::deserialize - Simplex_tree must be empty")); const char* ptr = buffer; - if constexpr (Options::is_multi_parameter) { - decltype(number_of_parameters_) num_params; - ptr = deserialize_trivial(num_params, ptr); - this->set_number_of_parameters(num_params); - } + // commented for the time the released gudhi version adds number_of_parameters_ + // multipers uses it to convert a gudhi.SimplexTree to a multipers.SimplexTree + // ptr = deserialize_value_from_char_buffer(number_of_parameters_, ptr); + // Needs to read size before recursivity to manage new siblings for children Vertex_handle members_size; - ptr = deserialize_trivial(members_size, ptr); - ptr = rec_deserialize(&root_, members_size, ptr, 0); + ptr = deserialize_value_from_char_buffer(members_size, ptr); + ptr = rec_deserialize(&root_, members_size, ptr, 0, deserialize_filtration_value); if (static_cast(ptr - buffer) != buffer_size) { throw std::invalid_argument("Deserialization does not match end of buffer"); } @@ -2681,17 +3026,24 @@ class Simplex_tree { private: /** \brief Serialize each element of the sibling and recursively call serialization. */ - const char* rec_deserialize(Siblings* sib, Vertex_handle members_size, const char* ptr, int dim) { - using namespace Gudhi::simplex_tree; + template + const char* rec_deserialize(Siblings* sib, + Vertex_handle members_size, + const char* ptr, + int dim, + [[maybe_unused]] F&& deserialize_filtration_value) + { // In case buffer is just a 0 char if (members_size > 0) { if constexpr (!Options::stable_simplex_handles) sib->members_.reserve(members_size); Vertex_handle vertex; - Filtration_value filtration(0); + // Set explicitly to zero to avoid false positive error raising in debug mode when store_filtration == false + // and to force array like Filtration_value value to be empty. + Filtration_value filtration(Gudhi::simplex_tree::empty_filtration_value); for (Vertex_handle idx = 0; idx < members_size; idx++) { - ptr = deserialize_trivial(vertex, ptr); - if (Options::store_filtration) { - ptr = deserialize_trivial(filtration, ptr); + ptr = deserialize_value_from_char_buffer(vertex, ptr); + if constexpr (Options::store_filtration) { + ptr = deserialize_filtration_value(filtration, ptr); } // Default is no children // If store_filtration is false, `filtration` is ignored. @@ -2700,11 +3052,11 @@ class Simplex_tree { Vertex_handle child_size; for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { update_simplex_tree_after_node_insertion(sh); - ptr = deserialize_trivial(child_size, ptr); + ptr = deserialize_value_from_char_buffer(child_size, ptr); if (child_size > 0) { Siblings* child = new Siblings(sib, sh->first); sh->second.assign_children(child); - ptr = rec_deserialize(child, child_size, ptr, dim + 1); + ptr = rec_deserialize(child, child_size, ptr, dim + 1, deserialize_filtration_value); } } if (dim > dimension_) { @@ -2717,44 +3069,49 @@ class Simplex_tree { private: Vertex_handle null_vertex_; - /** \brief Total number of simplices in the complex, without the empty simplex.*/ /** \brief Set of simplex tree Nodes representing the vertices.*/ Siblings root_; + + // all mutable as their content has no impact on the content of the simplex tree itself + // they correspond to some kind of cache or helper attributes. /** \brief Simplices ordered according to a filtration.*/ - std::vector filtration_vect_; + mutable std::vector filtration_vect_; /** \brief Upper bound on the dimension of the simplicial complex.*/ - int dimension_; - bool dimension_to_be_lowered_ = false; - - // MULTIPERS STUFF - public: - /** - * \brief Sets the number of parameters of the filtrations if SimplexTreeOptions::is_multi_parameter. - * */ - void set_number_of_parameters(int num) { - static_assert(SimplexTreeOptions::is_multi_parameter, - "Cannot set number of parameters of 1-parameter simplextree."); - number_of_parameters_ = num; - } - - /** - * \brief Gets the number of parameters of the filtrations if SimplexTreeOptions::is_multi_parameter. - * */ - int get_number_of_parameters() const { - if constexpr (SimplexTreeOptions::is_multi_parameter) - return number_of_parameters_; - else - return 1; - } - - private: - int number_of_parameters_; /**< Number of parameters of the multi-filtrations when - SimplexTreeOptions::is_multi_parameter.-*/ + mutable int dimension_; + mutable bool dimension_to_be_lowered_; + int number_of_parameters_; /**< Stores the number of parameters set by the user. */ }; // Print a Simplex_tree in os. -template -std::ostream& operator<<(std::ostream& os, Simplex_tree& st) { +template +std::ostream& operator<<(std::ostream & os, const Simplex_tree & st) { + using handle = typename Simplex_tree::Simplex_handle; + + if (st.num_parameters() > 1) os << st.num_parameters() << "\n"; + + // lexicographical order to ensure total order even with custom filtration values + st.initialize_filtration( + [&](handle sh1, handle sh2) { + if (st.dimension(sh1) < st.dimension(sh2)) return true; + if (st.dimension(sh1) > st.dimension(sh2)) return false; + + auto rg1 = st.simplex_vertex_range(sh1); + auto rg2 = st.simplex_vertex_range(sh2); + auto it1 = rg1.begin(); + auto it2 = rg2.begin(); + // same size + while (it1 != rg1.end()) { + if (*it1 == *it2) { + ++it1; + ++it2; + } else { + return *it1 < *it2; + } + } + return false; + }, + [](handle) -> bool { return false; }); + for (auto sh : st.filtration_simplex_range()) { os << st.dimension(sh) << " "; for (auto v : st.simplex_vertex_range(sh)) { @@ -2762,6 +3119,7 @@ std::ostream& operator<<(std::ostream& os, Simplex_tree& st) { } os << st.filtration(sh) << "\n"; // TODO(VR): why adding the key ?? not read ?? << " " << st.key(sh) << " \n"; } + st.clear_filtration(); return os; } @@ -2771,6 +3129,23 @@ std::istream& operator>>(std::istream& is, Simplex_tree& st) { std::vector simplex; typename ST::Filtration_value fil; int max_dim = -1; + + // searching for number of parameters which are potentially specified in the first line + // if nothing is specified, the value is assumed to be 1 + std::string first_line; + auto pos = is.tellg(); + getline(is, first_line); + std::stringstream fl_stream; + fl_stream << first_line; + int num_param, dummy; + fl_stream >> num_param; // tries to retrieve first numerical value + if (fl_stream.fail()) throw std::invalid_argument("Incoming stream should not start with a non integer."); + fl_stream >> dummy; // if number of parameters were specified, this should fail + if (!fl_stream.fail()) { + num_param = 1; + is.seekg(pos, std::ios_base::beg); + } + while (read_simplex(is, simplex, fil)) { // read all simplices in the file as a list of vertices // Warning : simplex_size needs to be casted in int - Can be 0 @@ -2783,6 +3158,7 @@ std::istream& operator>>(std::istream& is, Simplex_tree& st) { simplex.clear(); } st.set_dimension(max_dim); + st.set_num_parameters(num_param); return is; } diff --git a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h index 71be462f..43e7c4be 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h +++ b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_iterators.h @@ -16,12 +16,11 @@ #include -#include #include // for std::pair namespace Gudhi { -/** \addtogroup simplex_tree +/** \addtogroup simplex_tree * Iterators and range types for the Simplex_tree. * @{ */ @@ -67,7 +66,7 @@ class Simplex_tree_simplex_vertex_iterator : public boost::iterator_facade< sib_ = sib_->oncles(); } - Siblings * sib_; + Siblings const* sib_; Vertex_handle v_; }; @@ -93,7 +92,7 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< } // any end() iterator - explicit Simplex_tree_boundary_simplex_iterator(SimplexTree * st) + explicit Simplex_tree_boundary_simplex_iterator(SimplexTree const* st) : last_(st->null_vertex()), next_(st->null_vertex()), sib_(nullptr), @@ -102,7 +101,7 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< } template - Simplex_tree_boundary_simplex_iterator(SimplexTree * st, SimplexHandle sh) + Simplex_tree_boundary_simplex_iterator(SimplexTree const* st, SimplexHandle sh) : last_(sh->first), next_(st->null_vertex()), sib_(nullptr), @@ -111,7 +110,7 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< // Only check once at the beginning instead of for every increment, as this is expensive. if constexpr (SimplexTree::Options::contiguous_vertices) GUDHI_CHECK(st_->contiguous_vertices(), "The set of vertices is not { 0, ..., n } without holes"); - Siblings * sib = st->self_siblings(sh); + Siblings const* sib = st->self_siblings(sh); next_ = sib->parent(); sib_ = sib->oncles(); if (sib_ != nullptr) { @@ -146,8 +145,8 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< return; } - Siblings * for_sib = sib_; - Siblings * new_sib = sib_->oncles(); + Siblings const* for_sib = sib_; + Siblings const* new_sib = sib_->oncles(); auto rit = suffix_.rbegin(); if constexpr (SimplexTree::Options::contiguous_vertices && !SimplexTree::Options::stable_simplex_handles) { if (new_sib == nullptr) { @@ -180,9 +179,9 @@ class Simplex_tree_boundary_simplex_iterator : public boost::iterator_facade< Vertex_handle last_; // last vertex of the simplex Vertex_handle next_; // next vertex to push in suffix_ Static_vertex_vector suffix_; - Siblings * sib_; // where the next search will start from + Siblings const* sib_; // where the next search will start from Simplex_handle sh_; // current Simplex_handle in the boundary - SimplexTree * st_; // simplex containing the simplicial complex + SimplexTree const* st_; // simplex containing the simplicial complex }; /** \brief Iterator over the simplices of the boundary of a simplex and their opposite vertices. @@ -206,7 +205,7 @@ class Simplex_tree_boundary_opposite_vertex_simplex_iterator : public boost::ite } // any end() iterator - explicit Simplex_tree_boundary_opposite_vertex_simplex_iterator(SimplexTree * st) + explicit Simplex_tree_boundary_opposite_vertex_simplex_iterator(SimplexTree const* st) : last_(st->null_vertex()), next_(st->null_vertex()), sib_(nullptr), @@ -215,7 +214,7 @@ class Simplex_tree_boundary_opposite_vertex_simplex_iterator : public boost::ite } template - Simplex_tree_boundary_opposite_vertex_simplex_iterator(SimplexTree * st, SimplexHandle sh) + Simplex_tree_boundary_opposite_vertex_simplex_iterator(SimplexTree const* st, SimplexHandle sh) : last_(sh->first), next_(st->null_vertex()), sib_(nullptr), @@ -224,7 +223,7 @@ class Simplex_tree_boundary_opposite_vertex_simplex_iterator : public boost::ite // Only check once at the beginning instead of for every increment, as this is expensive. if constexpr (SimplexTree::Options::contiguous_vertices) GUDHI_CHECK(st_->contiguous_vertices(), "The set of vertices is not { 0, ..., n } without holes"); - Siblings * sib = st->self_siblings(sh); + Siblings const* sib = st->self_siblings(sh); next_ = sib->parent(); sib_ = sib->oncles(); if (sib_ != nullptr) { @@ -258,8 +257,8 @@ class Simplex_tree_boundary_opposite_vertex_simplex_iterator : public boost::ite baov_.first = st_->null_simplex(); return; // ------>> } - Siblings * for_sib = sib_; - Siblings * new_sib = sib_->oncles(); + Siblings const* for_sib = sib_; + Siblings const* new_sib = sib_->oncles(); auto rit = suffix_.rbegin(); if constexpr (SimplexTree::Options::contiguous_vertices && !SimplexTree::Options::stable_simplex_handles) { if (new_sib == nullptr) { @@ -294,9 +293,9 @@ class Simplex_tree_boundary_opposite_vertex_simplex_iterator : public boost::ite Vertex_handle last_; // last vertex of the simplex Vertex_handle next_; // next vertex to push in suffix_ Static_vertex_vector suffix_; - Siblings * sib_; // where the next search will start from + Siblings const* sib_; // where the next search will start from std::pair baov_; // a pair containing the current Simplex_handle in the boundary and its opposite vertex - SimplexTree * st_; // simplex containing the simplicial complex + SimplexTree const* st_; // simplex containing the simplicial complex }; /*---------------------------------------------------------------------------*/ @@ -318,7 +317,7 @@ class Simplex_tree_complex_simplex_iterator : public boost::iterator_facade< st_(nullptr) { } - explicit Simplex_tree_complex_simplex_iterator(SimplexTree * st) + explicit Simplex_tree_complex_simplex_iterator(SimplexTree const* st) : sib_(nullptr), st_(st) { if (st == nullptr || st->root() == nullptr || st->root()->members().empty()) { @@ -369,8 +368,8 @@ class Simplex_tree_complex_simplex_iterator : public boost::iterator_facade< } Simplex_handle sh_; - Siblings * sib_; - SimplexTree * st_; + Siblings const* sib_; + SimplexTree const* st_; }; /** \brief Iterator over the simplices of the skeleton of a given @@ -394,7 +393,7 @@ class Simplex_tree_skeleton_simplex_iterator : public boost::iterator_facade< curr_dim_(0) { } - Simplex_tree_skeleton_simplex_iterator(SimplexTree * st, int dim_skel) + Simplex_tree_skeleton_simplex_iterator(SimplexTree const* st, int dim_skel) : sib_(nullptr), st_(st), dim_skel_(dim_skel), @@ -450,12 +449,109 @@ class Simplex_tree_skeleton_simplex_iterator : public boost::iterator_facade< } Simplex_handle sh_; - Siblings * sib_; - SimplexTree * st_; + Siblings const* sib_; + SimplexTree const* st_; int dim_skel_; int curr_dim_; }; +/** \brief Iterator over the simplices of the simplicial complex that match the dimension specified by the parameter. + * + * Forward iterator, value_type is SimplexTree::Simplex_handle.*/ +template +class Simplex_tree_dimension_simplex_iterator : public boost::iterator_facade< + Simplex_tree_dimension_simplex_iterator, + typename SimplexTree::Simplex_handle const, boost::forward_traversal_tag> { + public: + typedef typename SimplexTree::Simplex_handle Simplex_handle; + typedef typename SimplexTree::Siblings Siblings; + typedef typename SimplexTree::Vertex_handle Vertex_handle; + +// any end() iterator + Simplex_tree_dimension_simplex_iterator() + : sib_(nullptr), + st_(nullptr), + dim_(0), + curr_dim_(0) { + } + + Simplex_tree_dimension_simplex_iterator(SimplexTree const* st, int dim) + : sib_(nullptr), + st_(st), + dim_(dim), + curr_dim_(0) { + if (st == nullptr || st->root() == nullptr || st->root()->members().empty() || + st->upper_bound_dimension() < dim || dim < 0) { + st_ = nullptr; + } else { + sh_ = st->root()->members().begin(); + sib_ = st->root(); + until_leaf_or_dim(); + // if we reached a leaf that does not respect the required dimension - call increment + if (curr_dim_ != dim_) + increment(); + } + } + private: + friend class boost::iterator_core_access; + +// valid when iterating along the SAME boundary. + bool equal(Simplex_tree_dimension_simplex_iterator const& other) const { + if (other.st_ == nullptr) { + return (st_ == nullptr); + } + if (st_ == nullptr) { + return false; + } + return (&(sh_->second) == &(other.sh_->second)); + } + + Simplex_handle const& dereference() const { + return sh_; + } + + void until_leaf_or_dim() { + while (st_->has_children(sh_) && curr_dim_ != dim_) { + sib_ = sh_->second.children(); + sh_ = sib_->members().begin(); + ++curr_dim_; + } +#ifdef DEBUG_TRACES + std::clog << "Simplex_tree::dimension_simplex_range until_leaf_or_dim reached ("; + for (auto vertex : st_->simplex_vertex_range(sh_)) { + std::clog << vertex << ","; + } + std::clog << ")\n"; +#endif // DEBUG_TRACES + } + // Depth first traversal of the tree structure. Returns when reaching a simplex with a given dimension + void increment() { + ++sh_; + while (sh_ == sib_->members().end()) { + if (sib_->oncles() == nullptr) { + st_ = nullptr; + return; + } // reach the end + sh_ = sib_->oncles()->members().find(sib_->parent()); + sib_ = sib_->oncles(); + --curr_dim_; + ++sh_; + until_leaf_or_dim(); + } + // It seems we do it twice here, but necessary when coming from the constructor + until_leaf_or_dim(); + // if we reached a leaf that does not respect the dimension - recall increment + if (curr_dim_ != dim_) + increment(); + } + + Simplex_handle sh_; + Siblings const* sib_; + SimplexTree const* st_; + int dim_; + int curr_dim_; +}; + /** @}*/ // end addtogroup simplex_tree } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h index 6c5eb0f1..78319f14 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h +++ b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_node_explicit_storage.h @@ -43,13 +43,9 @@ struct GUDHI_EMPTY_BASE_CLASS_OPTIMIZATION Simplex_tree_node_explicit_storage typedef typename SimplexTree::Simplex_key Simplex_key; typedef typename SimplexTree::Simplex_data Simplex_data; - // Simplex_tree_node_explicit_storage() : children_(nullptr) {} + Simplex_tree_node_explicit_storage() : children_(nullptr) {} - // Simplex_tree_node_explicit_storage(Siblings* sib, const Filtration_value& filtration) : children_(sib) - // { - // this->assign_filtration(filtration); - // } - Simplex_tree_node_explicit_storage(Siblings* sib = nullptr, const Filtration_value& filtration = Filtration_value()) + Simplex_tree_node_explicit_storage(Siblings* sib, const Filtration_value& filtration = Filtration_value()) : SimplexTree::Filtration_simplex_base(filtration), children_(sib) {} @@ -69,8 +65,12 @@ struct GUDHI_EMPTY_BASE_CLASS_OPTIMIZATION Simplex_tree_node_explicit_storage Siblings * children() { return children_; } + const Siblings * children() const { + return children_; + } Simplex_data& data() { return boost::empty_value::get(); } + const Simplex_data& data() const { return boost::empty_value::get(); } private: Siblings * children_; diff --git a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h index c0334ff0..d1e5d8bd 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h +++ b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_siblings.h @@ -41,6 +41,7 @@ class Simplex_tree_siblings { typedef typename SimplexTree::Node Node; typedef MapContainer Dictionary; typedef typename MapContainer::iterator Dictionary_it; + typedef typename MapContainer::const_iterator Dictionary_const_it; /* Default constructor.*/ Simplex_tree_siblings() @@ -73,10 +74,16 @@ class Simplex_tree_siblings { Dictionary_it find(Vertex_handle v) { return members_.find(v); } + Dictionary_const_it find(Vertex_handle v) const { + return members_.find(v); + } Simplex_tree_siblings * oncles() { return oncles_; } + const Simplex_tree_siblings * oncles() const { + return oncles_; + } Vertex_handle parent() const { return parent_; @@ -86,6 +93,10 @@ class Simplex_tree_siblings { return members_; } + const Dictionary & members() const { + return members_; + } + size_t size() const { return members_.size(); } @@ -94,6 +105,10 @@ class Simplex_tree_siblings { members_.erase(iterator); } + Dictionary_it to_non_const_it(Dictionary_const_it it) { + return members_.erase(it, it); + } + Simplex_tree_siblings * oncles_; Vertex_handle parent_; Dictionary members_; diff --git a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h index 1298223f..4b89adc0 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h +++ b/multipers/gudhi/gudhi/Simplex_tree/Simplex_tree_star_simplex_iterators.h @@ -68,12 +68,12 @@ class Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator class is_coface { public: is_coface() : cpx_(nullptr) {} - is_coface(SimplexTree* cpx, Static_vertex_vector&& simp) : cpx_(cpx), simp_(simp) {} + is_coface(SimplexTree const* cpx, Static_vertex_vector&& simp) : cpx_(cpx), simp_(simp) {} // Return true iff traversing the Node upwards to the root reads a // coface of simp_ - bool operator()(typename SimplexTree::Hooks_simplex_base& curr_hooks) { - Node& curr_node = static_cast(curr_hooks); + bool operator()(const typename SimplexTree::Hooks_simplex_base& curr_hooks) { + const Node& curr_node = static_cast(curr_hooks); Simplex_handle sh = cpx_->simplex_handle_from_node(curr_node); // first Node must always have label simp_.begin(); we assume it is true auto&& rng = cpx_->simplex_vertex_range(sh); @@ -85,25 +85,25 @@ class Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator } private: - SimplexTree* cpx_; + SimplexTree const* cpx_; Static_vertex_vector simp_; // vertices of simplex, in reverse order }; - typedef boost::filter_iterator + typedef boost::filter_iterator Filtered_cofaces_simplex_iterator; // any end() iterator Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator() : predicate_(), st_(nullptr) {} - Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator(SimplexTree* cpx, + Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator(SimplexTree const* cpx, Static_vertex_vector&& simp) : predicate_(cpx, std::move(simp)), st_(cpx) { GUDHI_CHECK(!simp.empty(), std::invalid_argument("cannot call for cofaces of an empty simplex")); - auto list_ptr = st_->nodes_by_label(simp.front()); + const auto list_ptr = st_->nodes_by_label(simp.front()); GUDHI_CHECK(list_ptr != nullptr, std::runtime_error("invalid call to cofaces forest")); it_ = boost::make_filter_iterator(predicate_, list_ptr->begin(), list_ptr->end()); end_ = boost::make_filter_iterator(predicate_, list_ptr->end(), list_ptr->end()); - Node& curr_node = static_cast(*it_); + const Node& curr_node = static_cast(*it_); sh_ = st_->simplex_handle_from_node(curr_node); } @@ -128,7 +128,7 @@ class Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator st_ = nullptr; } //== end else { // update sh_ - Node& curr_node = static_cast(*it_); + const Node& curr_node = static_cast(*it_); sh_ = st_->simplex_handle_from_node(curr_node); } } @@ -136,7 +136,7 @@ class Simplex_tree_optimized_cofaces_rooted_subtrees_simplex_iterator // given a Node of label max_v, returns true if the associated simplex is a coface of the simplex {..., max_v}. The // predicate stores the vertices of the simplex whose star we compute. is_coface predicate_; - SimplexTree* st_; + SimplexTree const* st_; // filtered iterators over Nodes, filtered with predicate_ Filtered_cofaces_simplex_iterator it_; Filtered_cofaces_simplex_iterator end_; @@ -170,7 +170,7 @@ class Simplex_tree_optimized_star_simplex_iterator // any end() iterator Simplex_tree_optimized_star_simplex_iterator() : st_(nullptr) {} - Simplex_tree_optimized_star_simplex_iterator(SimplexTree* cpx, Static_vertex_vector&& simp) + Simplex_tree_optimized_star_simplex_iterator(SimplexTree const* cpx, Static_vertex_vector&& simp) : st_(cpx), it_(cpx, std::move(simp)), end_(), sh_(*it_), sib_(st_->self_siblings(sh_)), children_stack_() { if (it_ == end_) { st_ = nullptr; @@ -256,7 +256,7 @@ class Simplex_tree_optimized_star_simplex_iterator // Let s be the simplex in a complex C whose star is // iterated through. Let max_v denote the maximal label of vertices in s. - SimplexTree* st_; // Simplex tree for complex C + SimplexTree const* st_; // Simplex tree for complex C // The cofaces of s form a subforest of the simplex tree. The roots of trees in this // forest have label max_v. //[it_,end_) == range of Simplex_handles of the roots of the cofaces trees (any dim) @@ -265,9 +265,9 @@ class Simplex_tree_optimized_star_simplex_iterator // curr Simplex_handle, returned by operator*, pointing to a coface of s Simplex_handle sh_; // set of siblings containing sh_ in the Simplex_tree - Siblings* sib_; // + Siblings const* sib_; // // Save children in a list to avoid calling sib_->members().find(.) - std::vector children_stack_; + std::vector children_stack_; // true iff sh_ points to the root of a coface subtree bool is_root_; }; diff --git a/multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h b/multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h new file mode 100644 index 00000000..58e59d62 --- /dev/null +++ b/multipers/gudhi/gudhi/Simplex_tree/filtration_value_utils.h @@ -0,0 +1,155 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef SIMPLEX_TREE_FILTRATION_VALUE_UTILS_H_ +#define SIMPLEX_TREE_FILTRATION_VALUE_UTILS_H_ + +#include // std::size_t +#include // std::numeric_limits +#include // std::isnan + +#include + +namespace Gudhi { + +namespace simplex_tree { + +/** + * @ingroup simplex_tree + * @brief Returns `Filtration_value(0)` when converted to `Filtration_value`. + */ +inline struct empty_filtration_value_t { + template explicit operator T() const { return T(0); } +} empty_filtration_value; + +} // namespace simplex_tree + +/** + * @ingroup simplex_tree + * @brief Returns true if and only if the given filtration value is at infinity. + * This is the overload for when @ref FiltrationValue is an arithmetic type, like double, int etc. It simply + * tests equality with `std::numeric_limits::infinity()` if defined or with + * `std::numeric_limits::max()` otherwise. Can therefore be also used with other classes + * as long as infinity is defined that way. + */ +template +bool is_positive_infinity(const Arithmetic_filtration_value& f) +{ + if constexpr (std::numeric_limits::has_infinity) { + return f == std::numeric_limits::infinity(); + } else { + return f == std::numeric_limits::max(); + } +} + +/** + * @ingroup simplex_tree + * @brief Given two filtration values at which a simplex exists, stores in the first value the minimal union of births + * generating a lifetime including those two values. + * This is the overload for when @ref FiltrationValue is an arithmetic type, like double, int etc. + * Because the filtration values are totally ordered then, the union is simply the minimum of the two values. + * + * NaN values are not supported. + */ +template +bool unify_lifetimes(Arithmetic_filtration_value& f1, const Arithmetic_filtration_value& f2) +{ + if (f2 < f1){ + f1 = f2; + return true; + } + return false; +} + +/** + * @ingroup simplex_tree + * @brief Given two filtration values, stores in the first value the lowest common upper bound of the two values. + * If a filtration value has value `NaN`, it should be considered as the lowest value possible. + * This is the overload for when @ref FiltrationValue is an arithmetic type, like double, float, int etc. + * Because the filtration values are totally ordered then, the upper bound is always the maximum of the two values. + */ +template +bool intersect_lifetimes(Arithmetic_filtration_value& f1, const Arithmetic_filtration_value& f2) +{ + if constexpr (std::numeric_limits::has_quiet_NaN) { + if (std::isnan(f1)) { + f1 = f2; + return !std::isnan(f2); + } + + // Computes the max while handling NaN as lowest value. + if (!(f1 < f2)) return false; + + f1 = f2; + return true; + } else { + // NaN not possible. + if (f1 < f2){ + f1 = f2; + return true; + } + return false; + } +} + +/** + * @private + * @ingroup simplex_tree + * @brief Serialize the given value and insert it at start position using + * @ref Gudhi::simplex_tree::serialize_trivial "". + * + * @tparam Trivial_filtration_value Type which can trivially be serialized with byte to byte copy of the content + * of the holding variable. E.g., native arithmetic types. + * @param[in] value The value to serialize. + * @param[in] start Start position where the value is serialized. + * @return The new position in the array of char for the next serialization. + * + * @warning It is the user's responsibility to provide a pointer to a buffer with enough memory space. + */ +template +char* serialize_value_to_char_buffer(Trivial_filtration_value value, char* start) { + return Gudhi::simplex_tree::serialize_trivial(value, start); +} + +/** + * @private + * @ingroup simplex_tree + * @brief Deserialize at the start position in an array of char and sets the value with it using + * @ref Gudhi::simplex_tree::deserialize_trivial "". + * + * @tparam Trivial_filtration_value Type which can trivially be serialized with byte to byte copy of the content + * of the holding variable. E.g., native arithmetic types. + * @param[in] value The value where to deserialize based on its type. + * @param[in] start Start position where the value is serialized. + * @return The new position in the array of char for the next deserialization. + * + * @warning It is the user's responsibility to ensure that the pointer will not go out of bounds. + */ +template +const char* deserialize_value_from_char_buffer(Trivial_filtration_value& value, const char* start) { + return Gudhi::simplex_tree::deserialize_trivial(value, start); +} + +/** + * @private + * @ingroup simplex_tree + * @brief Returns the size of the template type `Trivial_filtration_value`. + * + * @tparam Trivial_filtration_value Type which can trivially be serialized with byte to byte copy of the content + * of the holding variable. E.g., native arithmetic types. + */ +template +constexpr std::size_t get_serialization_size_of([[maybe_unused]] Trivial_filtration_value value) { + return sizeof(Trivial_filtration_value); +} + +} // namespace Gudhi + +#endif // SIMPLEX_TREE_FILTRATION_VALUE_UTILS_H_ diff --git a/multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h b/multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h index bff7d87e..ac250be0 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h +++ b/multipers/gudhi/gudhi/Simplex_tree/serialization_utils.h @@ -17,7 +17,10 @@ namespace Gudhi { namespace simplex_tree { -/** \brief Serialize the given value and insert it at start position. +/** + * @private + * @ingroup simplex_tree + * @brief Serialize the given value and insert it at start position. * * @param[in] value The value to serialize. * @param[in] start Start position where the value is serialized. @@ -32,7 +35,10 @@ char* serialize_trivial(ArgumentType value, char* start) { return start + arg_size; } -/** \brief Deserialize at the start position in an array of char and sets the value with it. +/** + * @private + * @ingroup simplex_tree + * \brief Deserialize at the start position in an array of char and sets the value with it. * * @param[in] value The value where to deserialize based on its type. * @param[in] start Start position where the value is serialized. @@ -47,14 +53,6 @@ const char* deserialize_trivial(ArgumentType& value, const char* start) { return (start + arg_size); } -/** - * @brief Returns the size of the serialization of the given object. - */ -template -constexpr std::size_t get_serialization_size_of([[maybe_unused]] ArgumentType value) { - return sizeof(ArgumentType); -} - } // namespace simplex_tree } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h b/multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h index a6be679f..cd52cfed 100644 --- a/multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h +++ b/multipers/gudhi/gudhi/Simplex_tree/simplex_tree_options.h @@ -15,7 +15,6 @@ #include #include // void_t -#include // std::isnan namespace Gudhi { @@ -40,7 +39,6 @@ struct Simplex_tree_options_default { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = false; }; /** Model of SimplexTreeOptions. @@ -57,7 +55,6 @@ struct Simplex_tree_options_full_featured { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = true; static const bool stable_simplex_handles = true; - static const bool is_multi_parameter = false; }; /** Model of SimplexTreeOptions. @@ -74,7 +71,6 @@ struct Simplex_tree_options_minimal { static const bool contiguous_vertices = false; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = false; }; /** @private @brief Model of SimplexTreeOptions, faster than `Simplex_tree_options_default` but note the unsafe @@ -92,7 +88,6 @@ struct Simplex_tree_options_fast_persistence { static const bool contiguous_vertices = true; static const bool link_nodes_by_label = false; static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = false; }; /** @}*/ // end addtogroup simplex_tree @@ -104,53 +99,6 @@ template struct Get_simplex_data_type { typedef No_simplex_d template struct Get_simplex_data_type> { typedef typename O::Simplex_data type; }; -/** - * @brief Given two filtration values at which a simplex exists, stores in the first value the minimal union of births - * generating a lifetime including those two values. - * This is the overload for when @ref FiltrationValue is an arithmetic type, like double, int etc. - * Because the filtration values are totally ordered then, the union is simply the minimum of the two values. - * - * NaN values are not supported. - */ -template -bool unify_lifetimes(Arithmetic_filtration_value& f1, Arithmetic_filtration_value f2) -{ - if (f2 < f1){ - f1 = f2; - return true; - } - return false; -} - -/** - * @brief Given two filtration values, stores in the first value the lowest common upper bound of the two values. - * If a filtration value has value `NaN`, it should be considered as the lowest value possible. - * This is the overload for when @ref FiltrationValue is an arithmetic type, like double, float, int etc. - * Because the filtration values are totally ordered then, the upper bound is always the maximum of the two values. - */ -template -bool intersect_lifetimes(Arithmetic_filtration_value& f1, Arithmetic_filtration_value f2) -{ - if constexpr (std::is_floating_point_v) { - if (std::isnan(f1)) { - f1 = f2; - return !std::isnan(f2); - } - - // Computes the max while handling NaN as lowest value. - if (!(f1 < f2)) return false; - - f1 = f2; - return true; - } else { - // NaN not possible. - if (f1 < f2){ - f1 = f2; - return true; - } - return false; - } -} } // namespace Gudhi diff --git a/multipers/gudhi/gudhi/Simplex_tree_multi.h b/multipers/gudhi/gudhi/Simplex_tree_multi.h deleted file mode 100644 index 5f6a9779..00000000 --- a/multipers/gudhi/gudhi/Simplex_tree_multi.h +++ /dev/null @@ -1,152 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which - * is released under MIT. See file LICENSE or go to - * https://gudhi.inria.fr/licensing/ for full license details. Author(s): David - * Loiseaux, Hannah Schreiber - * - * Copyright (C) 2023 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ -#ifndef SIMPLEX_TREE_MULTI_H_ -#define SIMPLEX_TREE_MULTI_H_ - -#include -#include -#include -#include - -namespace Gudhi { -namespace multi_persistence { - -/** Model of SimplexTreeOptions, with a multiparameter filtration. - * \ingroup multi_persistence - * */ -template -struct Simplex_tree_options_multidimensional_filtration { - public: - typedef linear_indexing_tag Indexing_tag; - typedef int Vertex_handle; - using Filtration_value = Filtration; - typedef typename Filtration::value_type value_type; - typedef std::uint32_t Simplex_key; - static const bool store_key = true; - static const bool store_filtration = true; - static const bool contiguous_vertices = false; - static const bool link_nodes_by_label = false; - static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = true; -}; - -/** - * \brief Turns a 1-parameter simplextree into a multiparameter simplextree, - * and keeps the 1-filtration in the 1st axis. - * Default values can be specified. - * \ingroup multi_persistence - * \tparam simplextree_std A non-multi simplextree - * \tparam simplextree_multi A multi simplextree - * \param st Simplextree to copy - * \param st_multi Multiparameter simplextree container to fill. - * \param default_values If given, this vector is assume to be of size `num_parameters-1` and contains the default - * values of axes `1` to `num_parameters`. - * */ -template -void multify(simplextree_std &st, - simplextree_multi &st_multi, - const int num_parameters, - const typename simplextree_multi::Options::Filtration_value &default_values = {}) { - typename simplextree_multi::Options::Filtration_value f(num_parameters); - static_assert(!simplextree_std::Options::is_multi_parameter && simplextree_multi::Options::is_multi_parameter, - "Can only convert non-multiparameter to multiparameter simplextree."); - unsigned int num_default_values = 0; - if constexpr (simplextree_multi::Options::Filtration_value::is_multi_critical) { - num_default_values = default_values[0].size(); - } else { - num_default_values = default_values.size(); - } - for (auto i = 0u; - i < std::min(num_default_values, num_parameters < 1 ? 0u : static_cast(num_parameters - 1)); - i++) { - if constexpr (simplextree_multi::Options::Filtration_value::is_multi_critical) { - f[0][i + 1] = default_values[0][i]; - } else { - f[i + 1] = default_values[i]; - } - } - - std::vector simplex; - simplex.reserve(st.dimension() + 1); - for (auto &simplex_handle : st.complex_simplex_range()) { - simplex.clear(); - for (auto vertex : st.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - - if (num_parameters > 0) { - if constexpr (simplextree_multi::Options::Filtration_value::is_multi_critical) { - f[0][0] = st.filtration(simplex_handle); - } else { - f[0] = st.filtration(simplex_handle); - } - } - st_multi.insert_simplex(simplex, f); - } - st_multi.set_number_of_parameters(num_parameters); -} - -/** - * \brief Turns a multiparameter-parameter simplextree into a 1-parameter - * simplextree. - * \ingroup multi_persistence - * \tparam simplextree_std A non-multi simplextree - * \tparam simplextree_multi A multi simplextree - * \param st Simplextree to fill. - * \param st_multi Multiparameter simplextree to convert into a 1 parameter simplex tree. - * \param dimension The filtration parameter to put into the 1 parameter simplextree. - * */ -template -void flatten(simplextree_std &st, simplextree_multi &st_multi, const int dimension = 0) { - static_assert(!simplextree_std::Options::is_multi_parameter && simplextree_multi::Options::is_multi_parameter, - "Can only convert multiparameter to non-multiparameter simplextree."); - for (const auto &simplex_handle : st_multi.complex_simplex_range()) { - std::vector simplex; - typename simplextree_multi::Options::value_type f; - for (auto vertex : st_multi.simplex_vertex_range(simplex_handle)) simplex.push_back(vertex); - if constexpr (simplextree_multi::Filtration_value::is_multi_critical) { - f = dimension >= 0 ? st_multi.filtration(simplex_handle)[0][dimension] : 0; - } else { - f = dimension >= 0 ? st_multi.filtration(simplex_handle)[dimension] : 0; - } - st.insert_simplex(simplex, f); - } -} - -// TODO: following not in the planned version in Gudhi. So either remove from here, or add there. - -/** - * \brief Applies a linear form (given by a scalar product, via Riesz - * representation) to the filtration values of the multiparameter simplextree to - * get a 1 parameter simplextree. \ingroup multiparameter \tparam - * simplextree_std A non-multi simplextree \tparam simplextree_multi A multi - * simplextree \param st Simplextree, with the same simplicial complex as - * st_multi, whose filtration has to be filled. \param st_multi Multiparameter - * simplextree to convert into a 1 parameter simplex tree. \param linear_form - * the linear form to apply. - * */ -template -void linear_projection(simplextree_std &st, simplextree_multi &st_multi, const std::vector &linear_form) { - static_assert(!simplextree_std::Options::is_multi_parameter && simplextree_multi::Options::is_multi_parameter, - "Can only convert multiparameter to non-multiparameter simplextree."); - auto sh = st.complex_simplex_range().begin(); - auto sh_multi = st_multi.complex_simplex_range().begin(); - auto end = st.complex_simplex_range().end(); - typename simplextree_multi::Options::Filtration_value multi_filtration; - for (; sh != end; ++sh, ++sh_multi) { - multi_filtration = st_multi.filtration(*sh_multi); - auto projected_filtration = compute_linear_projection(multi_filtration, linear_form); - st.assign_filtration(*sh, projected_filtration); - } -} - -} // namespace multi_persistence -} // namespace Gudhi - -#endif // SIMPLEX_TREE_MULTI_H_ diff --git a/multipers/gudhi/gudhi/Slicer.h b/multipers/gudhi/gudhi/Slicer.h new file mode 100644 index 00000000..41b32e09 --- /dev/null +++ b/multipers/gudhi/gudhi/Slicer.h @@ -0,0 +1,818 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - 2025/04 Hannah Schreiber: Reorganization + documentation. + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Slicer.h + * @author David Loiseaux + * @brief Contains the @ref Gudhi::multi_persistence::Slicer class. + */ + +#ifndef MP_SLICER_H_INCLUDED +#define MP_SLICER_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include //std::iota +#include //std::move +#include //std::int32_t +#include +#include +// #include //std::stringstream, to remove when to_str gets removed + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Slicer Slicer.h gudhi/Slicer.h + * @ingroup multi_persistence + * + * @brief Class encoding a filtered complex, presentation or resolution, inducing a multiparameter persistence module + * with: + * (1) a complex / presentation matrix / resolution matrix + * (2) a filtration (indexed by the complex) for each generator of (1), and + * (3) tools to compute 1-dimensional slices of the induced multi-parameter persistence module. + * + * TODO: more details + * + * @tparam MultiFiltrationValue Filtration value class respecting the @ref MultiFiltrationValue concept. + * @tparam PersistenceAlgorithm Class respecting the @ref PersistenceAlgorithm concept. Used to compute persistence, + * eventually vineyards and representative cycles. + */ +template +class Slicer +{ + public: + using Persistence = PersistenceAlgorithm; /**< Persistence algorithm type. */ + using Filtration_value = MultiFiltrationValue; /**< Filtration value type. */ + using T = typename Filtration_value::value_type; /**< Numerical filtration value element type. */ + using Complex = Multi_parameter_filtered_complex; /**< Complex type. */ + using Index = typename Complex::Index; /**< Complex index type. */ + using Dimension = typename Complex::Dimension; /**< Dimension type. */ + template + using Bar = Gudhi::persistence_matrix::Persistence_interval; /**< Bar type. */ + /** + * @brief Barcode type. A vector of @ref Bar, a tuple like structure containing birth, death and dimension of a bar. + */ + template + using Barcode = std::vector>; + /** + * @brief Flat barcode type. All bars are represented by a birth and a death value stored in arrays of size 2. + */ + template + using Flat_barcode = std::vector >; + /** + * @brief Barcode ordered by dimension type. A vector which has at index \f$ d \f$ the @ref Barcode of dimension + * \f$ d \f$. + */ + template + using Multi_dimensional_barcode = std::vector>; + /** + * @brief Flat barcode ordered by dimension type. A vector which has at index \f$ d \f$ the @ref Flat_barcode of + * dimension \f$ d \f$. + */ + template + using Multi_dimensional_flat_barcode = std::vector>; + using Cycle = std::vector; /**< Cycle type. */ + using Thread_safe = Thread_safe_slicer; /**< Thread safe slicer type. */ + + // CONSTRUCTORS + + /** + * @brief Default constructor. Constructs an empty slicer. + */ + Slicer() = default; + + /** + * @brief Constructs the slicer by copying the given complex. The current slice is not initialized to a default + * value, it can be set with @ref set_slice or @ref push_to. + * + * It is recommended to use a complex which is ordered by dimension for better performance. + */ + Slicer(const Complex& complex) + : complex_(complex), + slice_(complex.get_number_of_cycle_generators()), + generatorOrder_(complex.get_number_of_cycle_generators()), + persistence_() + {} + + /** + * @brief Constructs the slicer by moving the given complex. The current slice is not initialized to a default + * value, it can be set with @ref set_slice or @ref push_to. + * + * It is recommended to use a complex which is ordered by dimension for better performance. + */ + Slicer(Complex&& complex) + : complex_(std::move(complex)), + slice_(complex_.get_number_of_cycle_generators()), + generatorOrder_(complex_.get_number_of_cycle_generators()), + persistence_() + {} + + /** + * @brief Copy constructor. Persistence computation initialization is not updated. + */ + Slicer(const Slicer& other) + : complex_(other.complex_), slice_(other.slice_), generatorOrder_(other.generatorOrder_), persistence_() + {} + + /** + * @brief Move constructor. Persistence computation initialization is not updated. + */ + Slicer(Slicer&& other) noexcept + : complex_(std::move(other.complex_)), + slice_(std::move(other.slice_)), + generatorOrder_(std::move(other.generatorOrder_)), + persistence_() + {} + + ~Slicer() = default; + + /** + * @brief Assign operator. Persistence computation initialization is not updated. + */ + Slicer& operator=(const Slicer& other) + { + complex_ = other.complex_; + slice_ = other.slice_; + generatorOrder_ = other.generatorOrder_; + persistence_.reset(); + + return *this; + } + + /** + * @brief Move assign operator. Persistence computation initialization is not updated. + */ + Slicer& operator=(Slicer&& other) noexcept + { + complex_ = std::move(other.complex_); + slice_ = std::move(other.slice_); + generatorOrder_ = std::move(other.generatorOrder_); + persistence_.reset(); + + return *this; + } + + // TODO: swap ? + + // ACCESS + + /** + * @brief Returns a thread safe copy of this object. The copy is lighter than a real copy, as the complex is passed + * by pointer and gives access to all const method and persistence related methods. But the returned object will be + * invalidated if this object is destroyed. + */ + Thread_safe weak_copy() const { return Thread_safe(*this); } + + /** + * @brief Returns the number of generators in the stored module. + */ + Index get_number_of_cycle_generators() const { return complex_.get_number_of_cycle_generators(); } + + /** + * @brief Returns the number of parameters of the stored filtration. If the module is empty, the number returned is 0. + */ + Index get_number_of_parameters() const { return complex_.get_number_of_parameters(); } + + // // only used for scc io for now + // const Complex& get_chain_complex() const { return complex_; } + + /** + * @brief Returns a const reference to the current permutation map, indicating in which order are the generators + * with respect to the current slice (i.e., \$f order[i] \$f corresponds to the index in the complex of the + * \$f i^{th} \$f generator in the filtration represented by the slice). It will be initialized with + * @ref initialize_persistence_computation. + * + * If `ignoreInf` was true when calling @ref initialize_persistence_computation, indices of generators at infinity + * are not stored in the container. That means that the size can be smaller than what + * @ref get_number_of_cycle_generators returns. + */ + const std::vector& get_current_order() const { return generatorOrder_; } + + /** + * @brief Returns a const reference to the current slice. It can be initialized or updated with @ref set_slice + * and @ref push_to. + */ + const std::vector& get_slice() const { return slice_; } + + /** + * @brief Returns a reference to the current slice. It can also be initialized or updated with @ref set_slice + * and @ref push_to. + */ + std::vector& get_slice() { return slice_; } + + /** + * @brief Returns a const reference to the class computing the persistence of the current slice. It will be + * initialized with @ref initialize_persistence_computation. + */ + const Persistence& get_persistence_algorithm() const { return persistence_; } + + /** + * @brief Returns two filtration values representing respectively the greatest common lower bound of all filtration + * values in the filtration and the lowest common upper bound of them. + */ + std::pair get_bounding_box() const + { + Filtration_value a = Filtration_value::inf(get_number_of_parameters()); + Filtration_value b = Filtration_value::minus_inf(get_number_of_parameters()); + for (const Filtration_value& fil : complex_.get_filtration_values()) { + if (fil.num_generators() > 1) { + a.pull_to_greatest_common_lower_bound(factorize_below(fil)); + // Because of Degree_rips_bifiltration + Filtration_value above = factorize_above(fil); + auto g = above.num_generators() - 1; + b.push_to_least_common_upper_bound({above(g, 0), above(g, 1)}); + } else { + a.pull_to_greatest_common_lower_bound(fil); + b.push_to_least_common_upper_bound(fil); + } + } + return std::make_pair(std::move(a), std::move(b)); + } + + /** + * @brief Returns a const reference to the filtration value container. A filtration value at index \$f i \$f + * correspond to the filtration value associated to the generators at index \$f i \$f. + */ + const typename Complex::Filtration_value_container& get_filtration_values() const + { + return complex_.get_filtration_values(); + } + + /** + * @brief Returns a reference to the filtration value container. A filtration value at index \$f i \$f + * correspond to the filtration value associated to the generators at index \$f i \$f. + * + * @warning The container is not const such that the user can easily modify/update a filtration value. But do not + * modify the size of the container, nor the number of parameters. + */ + typename Complex::Filtration_value_container& get_filtration_values() { return complex_.get_filtration_values(); } + + /** + * @brief Returns a const reference to the filtration value associated to the generator at index \$f i \$f. + */ + const Filtration_value& get_filtration_value(Index i) const { return complex_.get_filtration_values()[i]; } + + /** + * @brief Returns a reference to the filtration value associated to the generator at index \$f i \$f. + * + * @warning The value is not const such that the user can easily modify/update the filtration value. But do not + * modify the number of parameters. + */ + Filtration_value& get_filtration_value(Index i) { return complex_.get_filtration_values()[i]; } + + /** + * @brief Returns a const reference to the dimension container. A value at index \$f i \$f corresponds to the + * dimension of the generator at index \$f i \$f. + */ + const std::vector& get_dimensions() const { return complex_.get_dimensions(); } + + /** + * @brief Returns the dimension of the generator at index \$f i \$f. + */ + Dimension get_dimension(Index i) const { return complex_.get_dimensions()[i]; } + + /** + * @brief Returns the maximal dimension of a generator in the module. + */ + Dimension get_max_dimension() const { return complex_.get_max_dimension(); } + + /** + * @brief Returns a const reference to the boundary container. The element at index \$f i \$f corresponds to the + * boundary of the generator at index \$f i \$f. + */ + const typename Complex::Boundary_container& get_boundaries() const { return complex_.get_boundaries(); } + + /** + * @brief Returns the boundary of the generator at index \$f i \$f. + */ + const typename Complex::Boundary& get_boundary(Index i) const { return complex_.get_boundaries()[i]; } + + // // TODO: only used to print info in python, so put in some interface instead + // std::string to_str() const + // { + // std::stringstream stream; + // stream << *this; + // return stream.str(); + // } + + // MODIFIERS + + /** + * @brief Sets the current slice, that is the 1-parameter filtration values associated to each generator on that line. + * The value at \$f slice[i] \$f has to corresponds to the value for the generator at index \$f i \$f. + * One can also sets the slice directly from the line with @ref push_to. + * + * @tparam Array Container with a begin() and end() method and whose element can be converted into `T`. + */ + template > + void set_slice(const Array& slice) + { + slice_ = std::vector(slice.begin(), slice.end()); + } + + /** + * @brief Sets the current slice by computing the 1-parameter filtration values fo each generator on the given line. + * + * @tparam Line_like Any type convertible to a @ref Line class. Default value: `std::initializer_list`. + */ + template > + void push_to(const Line_like& line) + { + _push_to(complex_, Line(line)); + } + + /** + * @brief Sets the current slice by computing the 1-parameter filtration values fo each generator on the given line. + * + * @tparam U Template parameter of the given line. + */ + template + void push_to(const Line& line) + { + _push_to(complex_, line); + } + + /** + * @brief Removes completely from the module all generator of dimension strictly higher than given. All + * initializations are invalidated, so the slice has to be reset and @ref initialize_persistence_computation recalled. + * + * @warning If the internal complex was not ordered by dimension, the complex is sorted before pruning. + * So, the indexing changes afterwards. + * + * @param maxDim Maximal dimension to keep. + */ + void prune_above_dimension(int maxDim) + { + int idx = complex_.prune_above_dimension(maxDim); + generatorOrder_.resize(idx); + generatorOrder_.shrink_to_fit(); + slice_.resize(idx); + persistence_.reset(); + } + + /** + * @brief Projects all filtration values into the given grid. If @p coordinate is false, the entries are set to + * the nearest upper bound value with the same parameter in the grid. Otherwise, the entries are set to the indices + * of those nearest upper bound values. + * An index \f$ i \f$ of the grid corresponds to the same parameter as the index \f$ i \f$ in a generator of the + * filtration value. The internal vectors correspond to the possible values of the parameters, ordered by increasing + * value, forming therefore all together a 2D grid. + * + * @param grid Vector of vector with size at least number of filtration parameters. + * @param coordinate If true, the values are set to the coordinates of the projection in the grid. If false, + * the values are set to the values at the coordinates of the projection. Default value: true. + */ + void coarsen_on_grid(const std::vector>& grid, bool coordinate = true) + { + complex_.coarsen_on_grid(grid, coordinate); + } + + // PERSISTENCE + + /** + * @brief Returns true if and only if @ref initialize_persistence_computation was properly called. + */ + [[nodiscard]] bool persistence_computation_is_initialized() const { return persistence_.is_initialized(); } + + /** + * @brief Initializes the persistence computation of the current slice. If the slice was not set properly as + * a valid 1-dimensional filtration, the behaviour is undefined. + * + * @param ignoreInf If true, all cells at infinity filtration values are ignored for the initialization, resulting + * potentially in less storage use and better performance. But note that this can be problematic with the use of + * @ref vineyard_update. Default value: true. + */ + void initialize_persistence_computation(const bool ignoreInf = true) + { + _initialize_persistence_computation(complex_, ignoreInf); + } + + /** + * @brief After the persistence computation was initialized for a slice and the slice changes, this method can + * update everything necessary for the barcode without re-computing everything from scratch (contrary to + * @ref initialize_persistence_computation). Furthermore, it guarantees that the new barcode will "match" the + * precedent one. TODO: explain exactly what it means and how to do the matching. + * The method will have better performance if the complex is ordered by dimension. + * + * Only available if PersistenceAlgorithm::is_vine is true. + * + * @pre @ref initialize_persistence_computation has to be called at least once before. + * + * @warning If `ignoreInf` was set to true when initializing the persistence computation, any update of the slice has + * to keep at infinity the boundaries which were before, otherwise the behaviour is undefined (it will throw with + * high probability). + */ + void vineyard_update() + { + static_assert(Persistence::is_vine, "vineyard_update() not enabled by the chosen PersistenceAlgorithm class."); + + const bool is_ordered_by_dim = complex_.is_ordered_by_dimension(); + // speed up when ordered by dim, to avoid unnecessary swaps + auto dim_condition = [&](int curr) { + if (is_ordered_by_dim) { + return persistence_.get_dimension(curr) == persistence_.get_dimension(curr - 1); + } + return true; + }; + for (Index i = 1; i < generatorOrder_.size(); i++) { + int curr = i; + while (curr > 0 && dim_condition(curr) && slice_[generatorOrder_[curr]] < slice_[generatorOrder_[curr - 1]]) { + persistence_.vine_swap(curr - 1); + std::swap(generatorOrder_[curr - 1], generatorOrder_[curr]); + --curr; + } + } + } + + /** + * @brief Returns the barcode of the current slice. The barcode format will change depending on the template values. + * + * @pre @ref initialize_persistence_computation has to be called at some point before. + * + * @tparam byDim If true, the barcode is returned as @ref Multi_dimensional_barcode, otherwise as @ref Barcode. + * @tparam Value Type of the birth and death values. + * @param maxDim Maximal dimension to be included in the barcode. If negative, all dimensions are included. + * Default value: -1. + */ + template + std::conditional_t, Barcode> get_barcode(int maxDim = -1) + { + if (maxDim < 0) maxDim = get_max_dimension(); + if constexpr (byDim) { + return _get_barcode_by_dim(maxDim); + } else { + return _get_barcode(maxDim); + } + } + + /** + * @brief Returns the barcode of the current slice. The barcode format will change depending on the template values. + * + * @pre @ref initialize_persistence_computation has to be called at some point before. + * + * @tparam byDim If true, the barcode is returned as @ref Multi_dimensional_flat_barcode, otherwise as + * @ref Flat_barcode. + * @tparam Value Type of the birth and death values. + * @param maxDim Maximal dimension to be included in the barcode. If negative, all dimensions are included. + * Default value: -1. + */ + template + std::conditional_t, Flat_barcode> get_flat_barcode( + int maxDim = -1) + { + if (maxDim < 0) maxDim = get_max_dimension(); + if constexpr (byDim) { + return _get_flat_barcode_by_dim(maxDim); + } else { + return _get_flat_barcode(maxDim); + } + } + + /** + * @brief Returns the representative cycles of the current slice. All cycles of dimension \f$ d \f$ are stored at + * index \f$ d \f$ of the returned vector. A cycle is represented by a vector of boundary indices. That is, the index + * \f$ i \f$ in a cycle represents the cell which boundary can be retrieved by @ref get_boundary "get_boundary(i)". + * + * Only available if PersistenceAlgorithm::has_rep_cycles is true. + * + * @pre @ref initialize_persistence_computation has to be called at least once before. + * + * @param update If true, updates the stored representative cycles, otherwise just returns the container in its + * current state. So should be true at least the first time the method is used. + */ + std::vector> get_representative_cycles(bool update = true) + { + return _get_representative_cycles(complex_, update); + } + + Cycle get_most_persistent_cycle(Dimension dim = 1, bool update = true) + { + static_assert(Persistence::has_rep_cycles, + "Representative cycles not enabled by the chosen PersistenceAlgorithm class."); + + auto barcodeIndices = persistence_.get_barcode(); + + Index maxIndex = -1; + Index maxBirth = std::numeric_limits::max(); + T maxLength = 0; + for (Index i = 0; i < barcodeIndices.size(); ++i) { + // barcodeIndices[i] does not work + const auto& bar = barcodeIndices(i); + if (bar.dim == dim) { + if (bar.death == Persistence::nullDeath) { + if (maxBirth > bar.birth) { + maxBirth = bar.birth; + maxIndex = i; + maxLength = Filtration_value::T_inf; + } + } else { + T length = std::abs(slice_[bar.death] - slice_[bar.birth]); + if (maxLength < length) { + maxLength = length; + maxIndex = i; + } + } + } + } + + if (maxIndex == static_cast(-1)) return {}; + + return persistence_.get_representative_cycle(maxIndex, update); + } + + // FRIENDS + + /** + * @brief Builds a new slicer by reordering the cells in the complex of the given slicer with the given permutation + * map. + */ + friend Slicer build_permuted_slicer(const Slicer& slicer, const std::vector& permutation) + { + GUDHI_CHECK(permutation.size() < slicer.get_number_of_cycle_generators(), + std::invalid_argument( + "Too many elements in permutation vector. Got perm size: " + std::to_string(permutation.size()) + + " while this->size: " + std::to_string(slicer.get_number_of_cycle_generators()))); + return Slicer(build_permuted_complex(slicer.complex_, permutation)); + } + + /** + * @brief Builds a new slicer by reordering the cells in the complex of the given slicer the same way than + * @ref Multi_parameter_filtered_complex::sort_by_dimension_co_lexicographically. Returns a pair with the new slicer + * as first element and the permutation map used as second element. + */ + friend std::pair> build_permuted_slicer(const Slicer& slicer) + { + auto [complex, permutation] = build_permuted_complex(slicer.complex_); + return std::make_pair(Slicer(std::move(complex)), std::move(permutation)); + } + + /** + * @brief Builds a new slicer from the given one by projecting its filtration values on a grid. + * See @ref coarsen_on_grid with the paramater `coordinate` at true. + */ + friend auto build_slicer_coarsen_on_grid(const Slicer& slicer, const std::vector> grid) + { + using return_filtration_value = decltype(std::declval().template as_type()); + using return_complex = decltype(build_complex_coarsen_on_grid(slicer.complex_, grid)); + using return_pers = typename Persistence::template As_type; + return Slicer(build_complex_coarsen_on_grid(slicer.complex_, grid)); + } + + /** + * @brief Builds a new slicer using @ref Projective_cover_kernel. TODO: explain what that means. + */ + friend Slicer build_slicer_from_projective_cover_kernel(const Slicer& slicer, Dimension dim) + { + Projective_cover_kernel kernel(slicer.complex_, dim); + return Slicer(kernel.create_complex()); + } + + /** + * @brief Writes the given slicer into a file with scc format. Assumes that every index appearing in a boundary of + * the complex corresponds to an existing index in the complex (for example, the lowest dimension has always empty + * boundaries). + * See @ref build_slicer_from_scc_file to build a slicer from a scc format file. + * + * @param outFilePath Path with file name into which to write. + * @param slicer Slicer to write. Every index appearing in a boundary of the complex has to correspond to an + * existing index in the underlying complex. + * @param degree TODO Default value: -1. + * @param rivetCompatible Set to true if the written file has to be Rivet compatible. Note that Rivet only accepts + * bi-filtrations. Default value: false. + * @param ignoreLastGenerators Set to true, if the generators with last dimension in the list should be ignored + * (maximal dimension by default, minimal dimension if `reverse` is true). Default value: false. + * @param stripComments Set to true, if no comment should be written in the file (comments are lines starting with `#` + * and which are ignored when read). Default value: false. + * @param reverse Set to true if the generators should be written in increasing order of dimension instead of + * decreasing. Default value: false. + */ + friend void write_slicer_to_scc_file(const std::string& outFilePath, + const Slicer& slicer, + int degree = -1, + bool rivetCompatible = false, + bool ignoreLastGenerators = false, + bool stripComments = false, + bool reverse = false) + { + const Complex& cpx = + slicer.complex_.is_ordered_by_dimension() ? slicer.complex_ : build_permuted_complex(slicer.complex_).first; + write_complex_to_scc_file( + outFilePath, cpx, degree, rivetCompatible, ignoreLastGenerators, stripComments, reverse); + }; + + /** + * @brief Outstream operator. + */ + friend std::ostream& operator<<(std::ostream& stream, Slicer& slicer) + { + stream << "-------------------- Slicer \n"; + + stream << "--- Filtered complex \n"; + stream << slicer.complex_; + + stream << "--- Order \n"; + stream << "{"; + for (const auto& idx : slicer.generatorOrder_) stream << idx << ", "; + stream << "}" << '\n'; + + stream << "--- Current slice filtration\n"; + stream << "{"; + for (const auto& val : slicer.slice_) stream << val << ", "; + if (!slicer.slice_.empty()) stream << "\b" << "\b"; + stream << "}" << '\n'; + + stream << "--- PersBackend \n"; + stream << slicer.persistence_; + + return stream; + } + + protected: + friend Thread_safe; // Thread_safe will use the "_*" methods below instead of "*". + + // For ThreadSafe version + Slicer(const std::vector& slice, const std::vector& generatorOrder, const Persistence& persistence) + : complex_(), slice_(slice), generatorOrder_(generatorOrder), persistence_(persistence, generatorOrder_) + {} + + Slicer(std::vector&& slice, std::vector&& generatorOrder, Persistence&& persistence) + : complex_(), + slice_(std::move(slice)), + generatorOrder_(std::move(generatorOrder)), + persistence_(std::move(persistence), generatorOrder_) + {} + + template + void _push_to(const Complex& complex, const Line& line) + { + const auto& filtrationValues = complex.get_filtration_values(); + for (Index i = 0U; i < filtrationValues.size(); i++) { + slice_[i] = line.template compute_forward_intersection(filtrationValues[i]); + } + } + + void _initialize_persistence_computation(const Complex& complex, const bool ignoreInf = true) + { + _initialize_order(complex, ignoreInf); + persistence_.reinitialize(complex, generatorOrder_); + } + + std::vector> _get_representative_cycles(const Complex& complex, bool update = true) + { + static_assert(Persistence::has_rep_cycles, + "Representative cycles not enabled by the chosen PersistenceAlgorithm class."); + + const auto& dimensions = complex.get_dimensions(); + auto cycleKeys = persistence_.get_representative_cycles(update); + auto numCycles = cycleKeys.size(); + std::vector> out(complex.get_max_dimension() + 1); + for (auto& cyclesDim : out) cyclesDim.reserve(numCycles); + for (const auto& cycle : cycleKeys) { + GUDHI_CHECK(!cycle.empty(), "A cycle should not be empty..."); + // assumes cycle to be never empty & all faces have same dimension + out[dimensions[cycle[0]]].push_back(cycle); + } + return out; + } + + private: + Complex complex_; /**< Complex storing all boundaries, filtration values and dimensions. */ + std::vector slice_; /**< Filtration values of the current slice. The indices corresponds to those in complex_. */ + std::vector generatorOrder_; /**< Permutation map from current slice index to complex index. */ + Persistence persistence_; /**< Class for persistence computations. */ + + void _initialize_order(const Complex& complex, const bool ignoreInf = true) + { + const auto& dimensions = complex.get_dimensions(); + generatorOrder_.resize(complex.get_number_of_cycle_generators()); + std::iota(generatorOrder_.begin(), generatorOrder_.end(), 0); + std::sort(generatorOrder_.begin(), generatorOrder_.end(), [&](Index i, Index j) { + if (ignoreInf) { + if (slice_[i] != Filtration_value::T_inf && slice_[j] == Filtration_value::T_inf) return true; + // all elements at inf are considered equal + if (slice_[i] == Filtration_value::T_inf) return false; + } + if (dimensions[i] > dimensions[j]) return false; + if (dimensions[i] < dimensions[j]) return true; + // if filtration values are equal, we don't care about order, so considered the same object + return slice_[i] < slice_[j]; + }); + if (ignoreInf) { + Index end = generatorOrder_.size(); + while (end > 0 && slice_[generatorOrder_[end - 1]] == Filtration_value::T_inf) --end; + generatorOrder_.resize(end); + } + } + + template + void _retrieve_interval(const Interval& bar, Dimension& dim, Value& birth, Value& death) + { + const Value inf = Gudhi::multi_filtration::MF_T_inf; + dim = bar.dim; + if constexpr (idx) { + birth = bar.birth; + death = -1; + if (bar.death != Persistence::nullDeath) death = bar.death; + } else { + birth = slice_[bar.birth]; + death = inf; + if (bar.death != Persistence::nullDeath) death = slice_[bar.death]; + if (!(birth <= death)) { + birth = inf; + death = inf; + } + } + } + + template + Barcode _get_barcode(int maxDim) + { + auto barcodeIndices = persistence_.get_barcode(); + Barcode out(barcodeIndices.size()); + Index i = 0; + for (const auto& bar : barcodeIndices) { + if (bar.dim <= maxDim) { + _retrieve_interval(bar, out[i].dim, out[i].birth, out[i].death); + ++i; + } + } + out.resize(i); + return out; + } + + template + Multi_dimensional_barcode _get_barcode_by_dim(int maxDim) + { + // TODO: This doesn't allow for negative dimensions + // Hannah: not sure what this comment means ? + Multi_dimensional_barcode out(maxDim + 1); + Value birth, death; + Dimension dim; + for (const auto& bar : persistence_.get_barcode()) { + if (bar.dim <= maxDim) { + _retrieve_interval(bar, dim, birth, death); + out[dim].emplace_back(birth, death, dim); + } + } + return out; + } + + template + Flat_barcode _get_flat_barcode(int maxDim) + { + auto barcodeIndices = persistence_.get_barcode(); + Flat_barcode out(barcodeIndices.size()); + Index i = 0; + Dimension dim; // dummy + for (const auto& bar : barcodeIndices) { + if (bar.dim <= maxDim) { + _retrieve_interval(bar, dim, out[i][0], out[i][1]); + ++i; + } + } + out.resize(i); + return out; + } + + template + Multi_dimensional_flat_barcode _get_flat_barcode_by_dim(int maxDim) + { + Multi_dimensional_flat_barcode out(maxDim + 1); + Value birth, death; + Dimension dim; + for (const auto& bar : persistence_.get_barcode()) { + if (bar.dim <= maxDim) { + _retrieve_interval(bar, dim, birth, death); + out[dim].emplace_back(std::array{birth, death}); + } + } + return out; + } +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_SLICER_H_INCLUDED diff --git a/multipers/gudhi/gudhi/Thread_safe_slicer.h b/multipers/gudhi/gudhi/Thread_safe_slicer.h new file mode 100644 index 00000000..e64d7c15 --- /dev/null +++ b/multipers/gudhi/gudhi/Thread_safe_slicer.h @@ -0,0 +1,388 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - 2025/04 Hannah Schreiber: Reorganization + documentation. + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Thread_safe_slicer.h + * @author David Loiseaux + * @brief Contains the @ref Gudhi::multi_persistence::Thread_safe_slicer class. + */ + +#ifndef MP_THREAD_SAFE_SLICER_H_INCLUDED +#define MP_THREAD_SAFE_SLICER_H_INCLUDED + +#include +#include +#include + +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @class Thread_safe_slicer Thread_safe_slicer.h gudhi/Thread_safe_slicer.h + * @ingroup multi_persistence + * + * @brief A more or less "thread safe version" of @ref Slicer. Gives access to all its const methods and persistence + * related methods. Each instance will store a pointer to the original slicer, but will have its own copies of all + * persistence related containers. It corresponds therefore more to a "light/week copy" of a slicer. + * + * @tparam Slicer Underlying @ref Slicer type. + */ +template +class Thread_safe_slicer : private Slicer +{ + public: + using Persistence = typename Slicer::Persistence; /**< Persistence algorithm type. */ + using Filtration_value = typename Slicer::Filtration_value; /**< Filtration value type. */ + using T = typename Slicer::T; /**< Numerical filtration value element type. */ + using Complex = typename Slicer::Complex; /**< Complex type. */ + using Index = typename Slicer::Index; /**< Complex index type. */ + using Dimension = typename Slicer::Dimension; /**< Dimension type. */ + template + using Bar = typename Slicer::template Bar; /**< Bar type. */ + /** + * @brief Barcode type. A vector of @ref Bar, a tuple like structure containing birth, death and dimension of a bar. + */ + template + using Barcode = typename Slicer::template Barcode; + /** + * @brief Flat barcode type. All bars are represented by a birth and a death value stored respectively at even and + * odd indices of the vector. + */ + template + using Flat_barcode = typename Slicer::template Flat_barcode; + /** + * @brief Barcode ordered by dimension type. A vector which has at index \f$ d \f$ the @ref Barcode of dimension + * \f$ d \f$. + */ + template + using Multi_dimensional_barcode = typename Slicer::template Multi_dimensional_barcode; + /** + * @brief Flat barcode ordered by dimension type. A vector which has at index \f$ d \f$ the @ref Flat_barcode of + * dimension \f$ d \f$. + */ + template + using Multi_dimensional_flat_barcode = typename Slicer::template Multi_dimensional_flat_barcode; + using Cycle = typename Slicer::Cycle; /**< Cycle type. */ + using Thread_safe = Thread_safe_slicer; /**< Thread safe slicer type. */ + + // CONSTRUCTORS + + /** + * @brief Constructor. Will store a pointer to the given slicer and copy all persistence related container. + * + * @param slicer Original slicer. + */ + Thread_safe_slicer(const Slicer& slicer) + : Slicer(slicer.get_slice(), slicer.get_current_order(), slicer.get_persistence_algorithm()), slicer_(&slicer) + {} + + /** + * @brief Copy constructor. + */ + Thread_safe_slicer(const Thread_safe_slicer& slicer) + : Slicer(slicer.get_slice(), slicer.get_current_order(), slicer.get_persistence_algorithm()), + slicer_(slicer.slicer_) + {} + + /** + * @brief Move constructor. + */ + Thread_safe_slicer(Thread_safe_slicer&& slicer) noexcept + : Slicer(std::move(slicer.slice_), std::move(slicer.generatorOrder_), std::move(slicer.persistence_)), + slicer_(slicer.slicer_) + {} + + ~Thread_safe_slicer() = default; + + /** + * @brief Assign operator. + */ + Thread_safe_slicer& operator=(const Thread_safe_slicer& other) + { + if (this == &other) return *this; + + Slicer::slice_ = other.slice_; + Slicer::generatorOrder_ = other.generatorOrder_; + Slicer::persistence_.reinitialize(slicer_->complex_, Slicer::generatorOrder_); + slicer_ = other.slicer_; + + return *this; + } + + /** + * @brief Move assign operator. + */ + Thread_safe_slicer& operator=(Thread_safe_slicer&& other) noexcept = delete; + + // ACCESS + + /** + * @brief Returns a copy of this object. + */ + Thread_safe weak_copy() const { return Thread_safe_slicer(*this); } + + /** + * @brief Returns the number of generators in the stored module. + */ + Index get_number_of_cycle_generators() const { return slicer_->get_number_of_cycle_generators(); } + + /** + * @brief Returns the number of parameters of the stored filtration. If the module is empty, the number returned is 0. + */ + Index get_number_of_parameters() const { return slicer_->get_number_of_parameters(); } + + /** + * @brief Returns a const reference to the current permutation map, indicating in which order are the generators + * with respect to the current slice (i.e., \$f order[i] \$f corresponds to the index in the complex of the + * \$f i^{th} \$f generator in the filtration represented by the slice). It will be initialized with + * @ref initialize_persistence_computation. + * + * If `ignoreInf` was true when calling @ref initialize_persistence_computation, indices of generators at infinity + * are not stored in the container. That means that the size can be smaller than what + * @ref get_number_of_cycle_generators returns. + */ + const std::vector& get_current_order() const { return Slicer::get_current_order(); } + + /** + * @brief Returns a const reference to the current slice. It can be initialized or updated with @ref set_slice + * and @ref push_to. + */ + const std::vector& get_slice() const { return Slicer::get_slice(); } + + /** + * @brief Returns a reference to the current slice. It can also be initialized or updated with @ref set_slice + * and @ref push_to. + */ + std::vector& get_slice() { return Slicer::get_slice(); } + + /** + * @brief Returns a const reference to the class computing the persistence of the current slice. It will be + * initialized with @ref initialize_persistence_computation. + */ + const Persistence& get_persistence_algorithm() const { return Slicer::get_persistence_algorithm(); } + + /** + * @brief Returns two filtration values representing respectively the greatest common lower bound of all filtration + * values in the filtration and the lowest common upper bound of them. + */ + std::pair get_bounding_box() const { return slicer_->get_bounding_box(); } + + /** + * @brief Returns a const reference to the filtration value container. A filtration value at index \$f i \$f + * correspond to the filtration value associated to the generators at index \$f i \$f. + */ + const typename Complex::Filtration_value_container& get_filtration_values() const + { + return slicer_->get_filtration_values(); + } + + /** + * @brief Returns a const reference to the filtration value associated to the generator at index \$f i \$f. + */ + const Filtration_value& get_filtration_value(Index i) const { return slicer_->get_filtration_value(i); } + + /** + * @brief Returns a const reference to the dimension container. A value at index \$f i \$f corresponds to the + * dimension of the generator at index \$f i \$f. + */ + const std::vector& get_dimensions() const { return slicer_->get_dimensions(); } + + /** + * @brief Returns the dimension of the generator at index \$f i \$f. + */ + Dimension get_dimension(Index i) const { return slicer_->get_dimension(i); } + + /** + * @brief Returns the maximal dimension of a generator in the module. + */ + Dimension get_max_dimension() const { return slicer_->get_max_dimension(); } + + /** + * @brief Returns a const reference to the boundary container. The element at index \$f i \$f corresponds to the + * boundary of the generator at index \$f i \$f. + */ + const typename Complex::Boundary_container& get_boundaries() const { return slicer_->get_boundaries(); } + + /** + * @brief Returns the boundary of the generator at index \$f i \$f. + */ + const typename Complex::Boundary& get_boundary(Index i) const { return slicer_->get_boundary(i); } + + // MODIFIERS + + /** + * @brief Sets the current slice, that is the 1-parameter filtration values associated to each generator on that line. + * The value at \$f slice[i] \$f has to corresponds to the value for the generator at index \$f i \$f. + * One can also sets the slice directly from the line with @ref push_to. + * + * @tparam Array Container which can be converted into a vector of `T`. + */ + template > + void set_slice(const Array& slice) + { + Slicer::set_slice(slice); + } + + /** + * @brief Sets the current slice by computing the 1-parameter filtration values fo each generator on the given line. + * + * @tparam Line_like Any type convertible to a @ref Line class. Default value: `std::initializer_list`. + */ + template > + void push_to(const Line_like& line) + { + Slicer::_push_to(slicer_->complex_, Line(line)); + } + + /** + * @brief Sets the current slice by computing the 1-parameter filtration values fo each generator on the given line. + * + * @tparam U Template parameter of the given line. + */ + template + void push_to(const Line& line) + { + Slicer::_push_to(slicer_->complex_, line); + } + + // PERSISTENCE + + /** + * @brief Returns true if and only if @ref initialize_persistence_computation was properly called. + */ + [[nodiscard]] bool persistence_computation_is_initialized() const + { + return Slicer::persistence_computation_is_initialized(); + } + + /** + * @brief Initializes the persistence computation of the current slice. If the slice was not set properly as + * a valid 1-dimensional filtration, the behaviour is undefined. + * + * @param ignoreInf If true, all cells at infinity filtration values are ignored for the initialization, resulting + * potentially in less storage use and better performance. But note that this can be problematic with the use of + * @ref vineyard_update. Default value: true. + */ + void initialize_persistence_computation(const bool ignoreInf = true) + { + Slicer::_initialize_persistence_computation(slicer_->complex_, ignoreInf); + } + + /** + * @brief After the persistence computation was initialized for a slice and the slice changes, this method can + * update everything necessary for the barcode without re-computing everything from scratch (contrary to + * @ref initialize_persistence_computation). Furthermore, it guarantees that the new barcode will "match" the + * precedent one. TODO: explain exactly what it means and how to do the matching. + * The method will have better performance if the complex is ordered by dimension. + * + * Only available if PersistenceAlgorithm::is_vine is true. + * + * @pre @ref initialize_persistence_computation has to be called at least once before. + * + * @warning If `ignoreInf` was set to true when initializing the persistence computation, any update of the slice has + * to keep at infinity the boundaries which were before, otherwise the behaviour is undefined (it will throw with + * high probability). + */ + void vineyard_update() { Slicer::vineyard_update(); } + + /** + * @brief Returns the barcode of the current slice. The barcode format will change depending on the template values. + * + * @pre @ref initialize_persistence_computation has to be called at some point before. + * + * @tparam byDim If true, the barcode is returned as @ref Multi_dimensional_barcode, otherwise as @ref Barcode. + * @tparam Value Type of the birth and death values. + * @param maxDim Maximal dimension to be included in the barcode. If negative, all dimensions are included. + * Default value: -1. + */ + template + auto get_barcode(int maxDim = -1) + { + // complex in parent is empty, so maxDim needs to be initialized from the outside. + if (maxDim < 0) maxDim = slicer_->get_max_dimension(); + return Slicer::template get_barcode(maxDim); + } + + /** + * @brief Returns the barcode of the current slice. The barcode format will change depending on the template values. + * + * @pre @ref initialize_persistence_computation has to be called at some point before. + * + * @tparam byDim If true, the barcode is returned as @ref Multi_dimensional_flat_barcode, otherwise as + * @ref Flat_barcode. + * @tparam Value Type of the birth and death values. + * @param maxDim Maximal dimension to be included in the barcode. If negative, all dimensions are included. + * Default value: -1. + */ + template + auto get_flat_barcode(int maxDim = -1) + { + // complex in parent is empty, so maxDim needs to be initialized from the outside. + if (maxDim < 0) maxDim = slicer_->get_max_dimension(); + return Slicer::template get_flat_barcode(maxDim); + } + + /** + * @brief Returns the representative cycles of the current slice. All cycles of dimension \f$ d \f$ are stored at + * index \f$ d \f$ of the returned vector. A cycle is represented by a vector of boundary indices. That is, the index + * \f$ i \f$ in a cycle represents the cell which boundary can be retrieved by @ref get_boundary "get_boundary(i)". + * + * Only available if PersistenceAlgorithm::has_rep_cycles is true. + * + * @pre @ref initialize_persistence_computation has to be called at least once before. + * + * @param update If true, updates the stored representative cycles, otherwise just returns the container in its + * current state. So should be true at least the first time the method is used. + */ + std::vector> get_representative_cycles(bool update = true) + { + return Slicer::_get_representative_cycles(slicer_->complex_, update); + } + + // FRIENDS + + /** + * @brief Outstream operator. + */ + friend std::ostream& operator<<(std::ostream& stream, Thread_safe_slicer& slicer) + { + stream << "-------------------- Thread_safe_slicer \n"; + + stream << "--- Filtered complex \n"; + stream << slicer.slicer_->complex_; + + stream << "--- Order \n"; + stream << "{"; + for (const auto& idx : slicer.get_current_order()) stream << idx << ", "; + stream << "}" << '\n'; + + stream << "--- Current slice filtration\n"; + stream << "{"; + for (const auto& val : slicer.get_slice()) stream << val << ", "; + stream << "\b" << "\b"; + stream << "}" << '\n'; + + stream << "--- PersBackend \n"; + stream << slicer.persistence_; + + return stream; + } + + private: + Slicer const* slicer_; /**< Original slicer. */ +}; + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_THREAD_SAFE_SLICER_H_INCLUDED diff --git a/multipers/gudhi/gudhi/multi_simplex_tree_helpers.h b/multipers/gudhi/gudhi/multi_simplex_tree_helpers.h new file mode 100644 index 00000000..5ec7fa65 --- /dev/null +++ b/multipers/gudhi/gudhi/multi_simplex_tree_helpers.h @@ -0,0 +1,147 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - 2025/04 Hannah Schreiber: simplifications with new simplex tree constructors + name changes + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file multi_simplex_tree_helpers.h + * @author David Loiseaux + * @brief Contains the @ref Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration struct, + * as well as the two helper methods @ref Gudhi::multi_persistence::make_multi_dimensional and + * @ref Gudhi::multi_persistence::make_one_dimensional. + */ + +#ifndef MP_MULTI_SIMPLEX_TREE_HELPERS_H_ +#define MP_MULTI_SIMPLEX_TREE_HELPERS_H_ + +#include +#include + +#include +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @ingroup multi_persistence + * + * @brief Model of @ref SimplexTreeOptions. Same as @ref Gudhi::Simplex_tree_options_default but with a custom + * filtration value type. + * + * @tparam MultiFiltrationValue Has to respect the @ref FiltrationValue concept. + */ +template +struct Simplex_tree_options_multidimensional_filtration : Simplex_tree_options_default { + using Filtration_value = MultiFiltrationValue; +}; + +/** + * @ingroup multi_persistence + * + * @brief Constructs a multi-dimensional simplex tree from the given one-dimensional simplex tree. + * + * All simplices are copied from the one-dimensional simplex tree \f$ st \f$ to the multi-dimensional simplex tree + * \f$ st_multi \f$. To begin, all filtration values of \f$ st_multi \f$ are initialized to the given default value. + * Then, all filtration values of \f$ st \f$ are projected onto \f$ st_multi \f$ at the given dimension index. + * + * @tparam MultiDimSimplexTreeOptions Options for the multi-dimensional simplex tree. Should follow the + * @ref SimplexTreeOptions concept. It has to define a @ref FiltrationValue with the additional methods: + * `num_parameters()` which returns the number of parameters, `num_generators()` which returns the number of generators + * and `operator(g, p)` which return a (modifiable) reference to the \f$ p^{th} \f$ element of the \f$ g^{th} \f$ + * generator. It should also define a type `value_type` with the type of an element in the filtration value. + * @tparam OneDimSimplexTree Type of the one-dimensional @ref Gudhi::Simplex_tree. The `Filtration_value` type has to + * be convertible to the `Filtration_value::value_type` of `MultiDimSimplexTreeOptions`. + * @param st Simplex tree to project. + * @param default_value Default value of the multi-dimensional filtration values. Has therefore to contain at least + * one generator and as many parameters than the final tree should have. One of the elements of the first generator + * will take the value of the projected value, so make sure to initialize the default value such that the `operator()` + * makes the change of value possible. + * @param dimension Dimension index to which the filtration values should be projected onto. + */ +template +Simplex_tree make_multi_dimensional( + const OneDimSimplexTree &st, + const typename MultiDimSimplexTreeOptions::Filtration_value &default_value, + const std::size_t dimension = 0) +{ + using OneDimF = typename OneDimSimplexTree::Options::Filtration_value; + using MultiDimF = typename MultiDimSimplexTreeOptions::Filtration_value; + + static_assert(std::is_convertible_v, + "A filtration value of the one dimensional tree should be convertible to an element of a filtration " + "value of the multi dimensional simplex tree."); + + auto num_param = default_value.num_parameters(); + + GUDHI_CHECK(dimension < num_param, + "Given dimension is too high, it has to be smaller than the number of parameters."); + GUDHI_CHECK(default_value.num_generators() > 0, + "The default value for the filtration values should contain at least one generator."); + + auto translate = [&](const OneDimF &f) -> MultiDimF { + auto res = default_value; + res(0, dimension) = f; + return res; + }; + + Simplex_tree multi_st(st, translate); + multi_st.set_num_parameters(num_param); + + return multi_st; +} + +/** + * @ingroup multi_persistence + * + * @brief Constructs a one-dimensional simplex tree from the given multi-dimensional simplex tree. + * + * All simplices are copied from the multi-dimensional simplex tree \f$ st \f$ to the one-dimensional simplex tree + * \f$ st_one \f$. All filtration values of \f$ st_one \f$ are initialized with the element value at given dimension + * index of the first generator of the corresponding multi-dimensional filtration value in \f$ st \f$. + * + * @tparam OneDimSimplexTreeOptions Options for the one-dimensional simplex tree. Should follow the + * @ref SimplexTreeOptions concept. + * @tparam MultiDimSimplexTree Type of the multi-dimensional @ref Gudhi::Simplex_tree. It has to define a + * @ref FiltrationValue with the additional methods: `num_parameters()` which returns the number of parameters, + * `num_generators()` which returns the number of generators and `operator(g, p)` which returns the value of the + * \f$ p^{th} \f$ element of the \f$ g^{th} \f$ generator. It should also define a type `value_type` with the type of + * an element in the filtration value, which has to be convertible to `Filtration_value` of `OneDimSimplexTreeOptions`. + * @param st Simplex tree to project. + * @param dimension Dimension index in the first generator to project. + */ +template +Simplex_tree make_one_dimensional(const MultiDimSimplexTree &st, + const std::size_t dimension = 0) +{ + using OneDimF = typename OneDimSimplexTreeOptions::Filtration_value; + using MultiDimF = typename MultiDimSimplexTree::Options::Filtration_value; + + static_assert(std::is_convertible_v, + "An element of a filtration value of the multi dimensional tree should be convertible to a filtration " + "value of the one dimensional simplex tree."); + + auto translate = [dimension](const MultiDimF &f) -> OneDimF { + GUDHI_CHECK(dimension < f.num_parameters(), + "Given dimension is too high, it has to be smaller than the number of parameters."); + GUDHI_CHECK(f.num_generators() > 0, "A filtration value of the multi tree should contain at least one generator."); + return f(0, dimension); + }; + + Simplex_tree one_st(st, translate); + one_st.set_num_parameters(1); + + return one_st; +} + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_MULTI_SIMPLEX_TREE_HELPERS_H_ diff --git a/multipers/gudhi/gudhi/persistence_interval.h b/multipers/gudhi/gudhi/persistence_interval.h index d72cabda..957a75e3 100644 --- a/multipers/gudhi/gudhi/persistence_interval.h +++ b/multipers/gudhi/gudhi/persistence_interval.h @@ -59,7 +59,8 @@ struct Persistence_interval { * @param dim Dimension of the cycle. Default value: -1. */ Persistence_interval(Event_value birth = inf, Event_value death = inf, Dimension dim = -1) - : dim(dim), birth(birth), death(death) {} + : dim(dim), birth(birth), death(death) + {} Dimension dim; /**< Dimension of the cycle.*/ Event_value birth; /**< Birth value of the cycle. */ @@ -71,7 +72,8 @@ struct Persistence_interval { * @param stream outstream * @param interval interval to stream */ - inline friend std::ostream& operator<<(std::ostream& stream, const Persistence_interval& interval) { + friend std::ostream& operator<<(std::ostream& stream, const Persistence_interval& interval) + { stream << "[" << interval.dim << "] "; if constexpr (std::numeric_limits::has_infinity) { stream << interval.birth << " - " << interval.death; @@ -96,7 +98,8 @@ struct Persistence_interval { * @return Either the birth value if @p I == 0, the death value if @p I == 1 or the dimension if @p I == 2. */ template - constexpr auto& get() & noexcept { + constexpr auto& get() & noexcept + { static_assert(I < 3, "Value mismatch at argument 1 in template parameter list. Maximal possible value is 2."); if constexpr (I == 0) return birth; @@ -111,7 +114,8 @@ struct Persistence_interval { * @return Either the birth value if @p I == 0, the death value if @p I == 1 or the dimension if @p I == 2. */ template - constexpr const auto& get() const& noexcept { + constexpr const auto& get() const& noexcept + { static_assert(I < 3, "Value mismatch at argument 1 in template parameter list. Maximal possible value is 2."); if constexpr (I == 0) return birth; @@ -126,7 +130,8 @@ struct Persistence_interval { * @return Either the birth value if @p I == 0, the death value if @p I == 1 or the dimension if @p I == 2. */ template - constexpr auto&& get() && noexcept { + constexpr auto&& get() && noexcept + { static_assert(I < 3, "Value mismatch at argument 1 in template parameter list. Maximal possible value is 2."); if constexpr (I == 0) return std::move(birth); @@ -141,7 +146,8 @@ struct Persistence_interval { * @return Either the birth value if @p I == 0, the death value if @p I == 1 or the dimension if @p I == 2. */ template - constexpr const auto&& get() const&& noexcept { + constexpr const auto&& get() const&& noexcept + { static_assert(I < 3, "Value mismatch at argument 1 in template parameter list. Maximal possible value is 2."); if constexpr (I == 0) return std::move(birth); @@ -181,14 +187,14 @@ template struct tuple_element > { static_assert(I < 3, "Value mismatch at argument 1 in template parameter list. Maximal possible value is 2."); - using type = typename conditional ::type; + using type = conditional_t < I<2, Event_value, Dimension>; }; /** * @ingroup persistence_matrix * * @brief Partial specialization of `get` for @ref Gudhi::persistence_matrix::Persistence_interval. - * + * * @tparam I Index of the value to return: 0 for the birth value, 1 for the death value and 2 for the dimension. * @tparam Dimension First template parameter of @ref Gudhi::persistence_matrix::Persistence_interval. * @tparam Event_value Second template parameter of @ref Gudhi::persistence_matrix::Persistence_interval. @@ -196,7 +202,8 @@ struct tuple_element -constexpr auto& get(Gudhi::persistence_matrix::Persistence_interval& i) noexcept { +constexpr auto& get(Gudhi::persistence_matrix::Persistence_interval& i) noexcept +{ return i.template get(); } @@ -204,7 +211,7 @@ constexpr auto& get(Gudhi::persistence_matrix::Persistence_interval -constexpr const auto& get(const Gudhi::persistence_matrix::Persistence_interval& i) noexcept { +constexpr const auto& get(const Gudhi::persistence_matrix::Persistence_interval& i) noexcept +{ return i.template get(); } @@ -220,7 +228,7 @@ constexpr const auto& get(const Gudhi::persistence_matrix::Persistence_interval< * @ingroup persistence_matrix * * @brief Partial specialization of `get` for @ref Gudhi::persistence_matrix::Persistence_interval. - * + * * @tparam I Index of the value to return: 0 for the birth value, 1 for the death value and 2 for the dimension. * @tparam Dimension First template parameter of @ref Gudhi::persistence_matrix::Persistence_interval. * @tparam Event_value Second template parameter of @ref Gudhi::persistence_matrix::Persistence_interval. @@ -228,7 +236,8 @@ constexpr const auto& get(const Gudhi::persistence_matrix::Persistence_interval< * @return Either the birth value if @p I == 0, the death value if @p I == 1 or the dimension if @p I == 2. */ template -constexpr auto&& get(Gudhi::persistence_matrix::Persistence_interval&& i) noexcept { +constexpr auto&& get(Gudhi::persistence_matrix::Persistence_interval&& i) noexcept +{ return std::move(i).template get(); } @@ -236,7 +245,7 @@ constexpr auto&& get(Gudhi::persistence_matrix::Persistence_interval -constexpr const auto&& get(const Gudhi::persistence_matrix::Persistence_interval&& i) noexcept { +constexpr const auto&& get(const Gudhi::persistence_matrix::Persistence_interval&& i) noexcept +{ return std::move(i).template get(); } diff --git a/multipers/gudhi/gudhi/persistence_matrix_options.h b/multipers/gudhi/gudhi/persistence_matrix_options.h index d5638156..d349b344 100644 --- a/multipers/gudhi/gudhi/persistence_matrix_options.h +++ b/multipers/gudhi/gudhi/persistence_matrix_options.h @@ -17,7 +17,10 @@ #ifndef PM_OPTIONS_INCLUDED #define PM_OPTIONS_INCLUDED -#include "Fields/Zp_field_operators.h" +#include +#include + +#include namespace Gudhi { namespace persistence_matrix { @@ -27,7 +30,7 @@ namespace persistence_matrix { * * @brief List of column types. */ -enum class Column_types { +enum class Column_types : std::uint8_t { LIST, /**< @ref List_column "": Underlying container is a std::list<@ref Entry*>. */ SET, /**< @ref Set_column "": Underlying container is a std::set<@ref Entry*>. */ HEAP, /**< @ref Heap_column "": Underlying container is a std::vector<@ref Entry*> ordered as a heap. @@ -35,22 +38,37 @@ enum class Column_types { VECTOR, /**< @ref Vector_column "": Underlying container is a std::vector<@ref Entry*> with a lazy removal method. */ NAIVE_VECTOR, /**< @ref Naive_vector_column "": Underlying container is a std::vector<@ref Entry*>. */ + SMALL_VECTOR, /**< @ref Naive_vector_column "": Underlying container is a + boost::container::small_vector<@ref Entry*, 8>. */ UNORDERED_SET, /**< @ref Unordered_set_column "": Underlying container is a std::unordered_set<@ref Entry*>. */ INTRUSIVE_LIST, /**< @ref Intrusive_list_column "": Underlying container is a boost::intrusive::list<@ref Entry>. */ - INTRUSIVE_SET, /**< @ref Intrusive_set_column "": Underlying container is a boost::intrusive::set<@ref Entry>. */ - SMALL_VECTOR + INTRUSIVE_SET /**< @ref Intrusive_set_column "": Underlying container is a boost::intrusive::set<@ref Entry>. */ }; +// a column is said to behave well if its underlying content is alway ordered by increasing row index +// and iterators cannot iterate over zero cells. +template +struct is_well_behaved : std::true_type {}; + +template <> +struct is_well_behaved : std::false_type {}; // non ordered + +template <> +struct is_well_behaved : std::false_type {}; // lazy row clear + +template <> +struct is_well_behaved : std::false_type {}; // non ordered + /** * @ingroup persistence_matrix * * @brief List if indexation schemes. See @ref mp_indexation "description of indexation schemes" for more details * about the meaning of the indexation types. */ -enum class Column_indexation_types { - CONTAINER, /**< Default use of @ref MatIdx indices. */ - POSITION, /**< All input and output @ref MatIdx indices are replaced with @ref PosIdx indices. */ - IDENTIFIER /**< All input and output @ref MatIdx indices are replaced with @ref IDIdx indices. */ +enum class Column_indexation_types : std::uint8_t { + CONTAINER, /**< Default use of @ref MatIdx indices. */ + POSITION, /**< All input and output @ref MatIdx indices are replaced with @ref PosIdx indices. */ + IDENTIFIER /**< All input and output @ref MatIdx indices are replaced with @ref IDIdx indices. */ }; /** @@ -63,19 +81,18 @@ enum class Column_indexation_types { * * To create other matrix types, the easiest is to simply inherit from this structure and overwrite only the options * one is interested in. - * + * * @tparam col_type Column type for the matrix. Default value: @ref Column_types::INTRUSIVE_SET * @tparam is_z2_only Flag indicating if only \f$Z_2\f$ coefficient will be used with the matrix. Set to true if it * is the case, false otherwise. Default value: true. * @tparam FieldOperators Field operators used by the matrix, see FieldOperators concept. - * Only necessary if @p is_z2_only is false. + * Only necessary if @p is_z2_only is false. * Default value: @ref Gudhi::persistence_fields::Zp_field_operators<>. */ -template > -struct Default_options -{ +struct Default_options { using Field_coeff_operators = FieldOperators; using Dimension = int; using Index = unsigned int; @@ -103,17 +120,16 @@ struct Default_options static const bool can_retrieve_representative_cycles = false; }; -//TODO: The following structures are the one used by the other modules or debug tests. -// They will probably be removed once the module was properly integrated. +// TODO: The following structures are the one used by the other modules or debug tests. +// They will probably be removed once the module was properly integrated. /** * @brief Options used for the Zigzag persistence module. - * + * * @tparam column_type Column type for the matrix. */ template -struct Zigzag_options : Default_options -{ +struct Zigzag_options : Default_options { static const bool has_row_access = true; static const bool has_column_pairings = false; static const bool has_vine_update = true; @@ -125,40 +141,37 @@ struct Zigzag_options : Default_options /** * @brief Options needed to use the representative cycles. - * + * * @tparam col_type Column type for the matrix. */ template -struct Representative_cycles_options : Default_options -{ +struct Representative_cycles_options : Default_options { static const bool has_column_pairings = true; static const bool can_retrieve_representative_cycles = true; }; /** * @brief Options used by the Multipersistence module. - * + * * @tparam column_type Column type for the matrix. */ template -struct Multi_persistence_options : Default_options -{ +struct Multi_persistence_options : Default_options { static const bool has_column_pairings = true; static const bool has_vine_update = true; }; /** * @brief Options used by the cohomology module. - * + * * @tparam column_type Column type for the matrix. * @tparam is_z2_only True if Z2. * @tparam FieldOperators Field operator. */ -template > -struct Cohomology_persistence_options : Default_options -{ +struct Cohomology_persistence_options : Default_options { static const bool has_row_access = true; static const bool has_column_compression = true; static const bool has_removable_rows = true; diff --git a/multipers/gudhi/gudhi/simple_mdspan.h b/multipers/gudhi/gudhi/simple_mdspan.h new file mode 100644 index 00000000..e1e47e38 --- /dev/null +++ b/multipers/gudhi/gudhi/simple_mdspan.h @@ -0,0 +1,484 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, David Loiseaux + * + * Copyright (C) 2025 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @private + * @file simple_mdspan.h + * @author Hannah Schreiber, David Loiseaux + */ + +#ifndef GUDHI_SIMPLE_MDSPAN_H_ +#define GUDHI_SIMPLE_MDSPAN_H_ + +#include // std::size_t +#include +#include // std::remove_cv_t, std::make_unsigned_t, std::integral_constant +#include +#include +#include +#include + +#include + +namespace Gudhi { + +inline constexpr std::size_t dynamic_extent = std::numeric_limits::max(); + +template +class extents; + +namespace detail { + + template + struct is_dynamic : std::integral_constant {}; + + template <> + struct is_dynamic : std::integral_constant {}; + + template + struct dynamic_count; + + template + struct dynamic_count > + : std::integral_constant::value + + dynamic_count>::value)> { + }; + + template + struct dynamic_count<0, std::integer_sequence > + : std::integral_constant::value> { + }; + + template + struct dynamic_count<0, std::integer_sequence > + : std::integral_constant::value> {}; + + template + struct extent_value; + + template + struct extent_value> + : extent_value> {}; + + template + struct extent_value<0, std::integer_sequence> + : std::integral_constant {}; + + template + struct dynamic_value_sequence { + using type = typename dynamic_value_sequence::type; + }; + + template + struct dynamic_value_sequence { + using type = std::integer_sequence; + }; + + template + constexpr auto dynamic_value_extents(std::integer_sequence) + { + return extents(); + }; + + template + constexpr auto dynamic_value_extents_value = + dynamic_value_extents((typename Gudhi::detail::dynamic_value_sequence::type{})); + +} // namespace detail + +template +using dextents = decltype(detail::dynamic_value_extents_value); + +/** + * @private + * @brief Reproduces the behaviour of C++23 `std::extents` class. + */ +template +class extents +{ + public: + using index_type = IndexType; + using size_type = std::make_unsigned_t; + using rank_type = std::size_t; + + // observers of the multidimensional index space + static constexpr rank_type rank() noexcept { return sizeof...(Extents); } + + static constexpr rank_type rank_dynamic() noexcept + { + return detail::dynamic_count >::value; + } + + static constexpr std::size_t static_extent(rank_type r) noexcept + { + std::array exts{Extents...}; + return exts[r]; + } + + constexpr index_type extent(rank_type r) const noexcept + { + if (dynamic_extent_shifts_[r] < 0) return static_extent(r); + return dynamic_extents_[dynamic_extent_shifts_[r]]; + } + + void update_dynamic_extent(rank_type r, index_type i){ + if (dynamic_extent_shifts_[r] < 0) throw std::invalid_argument("Given rank is not dynamic."); + dynamic_extents_[dynamic_extent_shifts_[r]] = i; + } + + // constructors + constexpr extents() noexcept : dynamic_extents_(), dynamic_extent_shifts_(_init_shifts()) {} + + template + constexpr explicit extents(const extents& other) noexcept + : dynamic_extents_(), dynamic_extent_shifts_(_init_shifts()) + { + for (rank_type r = 0; r < rank(); ++r) { + if (dynamic_extent_shifts_[r] >= 0) dynamic_extents_[dynamic_extent_shifts_[r]] = other.extent(r); + } + } + + template + constexpr explicit extents(OtherIndexTypes... extents) noexcept + : dynamic_extents_{static_cast(extents)...}, dynamic_extent_shifts_(_init_shifts()) + {} + + template + constexpr explicit extents(const std::array& other) noexcept + : dynamic_extents_{other}, dynamic_extent_shifts_(_init_shifts()) + {} + + // comparison operators + template + friend constexpr bool operator==(const extents& e1, const extents& e2) noexcept + { + if (e1.rank() != e2.rank()) return false; + for (rank_type r = 0; r < rank(); ++r) { + if (e1.extent(r) != e2.extent(r)) return false; + } + return true; + } + + friend void swap(extents& e1, extents& e2) noexcept + { + e1.dynamic_extents_.swap(e2.dynamic_extents_); + e1.dynamic_extent_shifts_.swap(e2.dynamic_extent_shifts_); + } + + friend std::ostream &operator<<(std::ostream &stream, const extents &e) + { + stream << "[ " << sizeof...(Extents) << " ] "; + ((stream << Extents << ' '), ...); + + return stream; + } + + private: + std::array dynamic_extents_; + std::array dynamic_extent_shifts_; + + static constexpr std::array _init_shifts() + { + std::array exts{Extents...}; + std::array res = {}; + std::size_t index = 0; + for (rank_type i = 0; i < rank(); ++i) { + if (exts[i] == dynamic_extent) { + res[i] = index; + ++index; + } else { + res[i] = -1; + } + } + return res; + } +}; + +// Does not seem to work with C++17(?) because the use of 'dextents' is not explicit enough: +// "trailing return type ‘Gudhi::dextents’ of deduction guide is not a +// specialization of ‘Gudhi::extents’" +// Or does someone knows a workaround...? +// template +// explicit extents(Integrals...) -> dextents; + +/** + * @private + * @brief Reproduces the behaviour of C++23 `std::layout_right` class. + */ +class layout_right +{ + public: + template + class mapping + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_right; + + // constructors + mapping() noexcept = default; + mapping(const mapping&) noexcept = default; + + mapping(const extents_type& exts) noexcept : exts_(exts) + { + if constexpr (extents_type::rank() != 0) _initialize_strides(); + } + + mapping& operator=(const mapping&) noexcept = default; + + // observers + constexpr const extents_type& extents() const noexcept { return exts_; } + + index_type required_span_size() const noexcept + { + if constexpr (extents_type::rank() == 0) return 0; + else return ext_shifts_[0] * exts_.extent(0); + } + + template + constexpr index_type operator()(Indices... indices) const + { + return operator()({static_cast(indices)...}); + } + + template > + constexpr index_type operator()(const IndexRange& indices) const + { + GUDHI_CHECK(indices.size() == extents_type::rank(), "Wrong number of parameters."); + + index_type newIndex = 0; + auto it = indices.begin(); + GUDHI_CHECK_code(unsigned int i = 0); + for (auto stride : ext_shifts_) { + GUDHI_CHECK_code(GUDHI_CHECK(*it < exts_.extent(i), "Out of bound index.")); + newIndex += (stride * (*it)); + ++it; + GUDHI_CHECK_code(++i); + } + + return newIndex; + } + + static constexpr bool is_always_unique() noexcept { return true; } + + static constexpr bool is_always_exhaustive() noexcept { return true; } + + static constexpr bool is_always_strided() noexcept { return true; } + + static constexpr bool is_unique() noexcept { return true; } + + static constexpr bool is_exhaustive() noexcept { return true; } + + static constexpr bool is_strided() noexcept { return true; } + + index_type stride(rank_type r) const + { + GUDHI_CHECK(r < ext_shifts_.size(), "Stride out of bound."); + return ext_shifts_[r]; + } + + friend bool operator==(const mapping& m1, const mapping& m2) noexcept { return m1.exts_ == m2.exts_; } + + friend void swap(mapping& m1, mapping& m2) noexcept + { + swap(m1.exts_, m2.exts_); + m1.ext_shifts_.swap(m2.ext_shifts_); + } + + // update can be faster than reconstructing everytime if only relatively small r's are updated. + void update_extent(rank_type r, index_type new_value) + { + GUDHI_CHECK(r < extents_type::rank(), "Index out of bound."); + exts_.update_dynamic_extent(r, new_value); + _update_strides(r); + } + + private: + extents_type exts_; + std::array ext_shifts_; + + constexpr void _initialize_strides() + { + ext_shifts_[extents_type::rank() - 1] = 1; + for (auto i = extents_type::rank() - 1; i > 0; --i) { + ext_shifts_[i - 1] = ext_shifts_[i] * exts_.extent(i); + } + } + + constexpr void _update_strides(rank_type start) + { + for (auto i = start; i > 0; --i) { + ext_shifts_[i - 1] = ext_shifts_[i] * exts_.extent(i); + } + } + }; +}; + +/** + * @private + * @brief Simplified version of C++23 `std::mdspan` class that compiles with C++17. + * + * Main differences: + * - there is no AccessorPolicy template: the container pointed by the stored pointer is assumed to be vector-like, + * i.e., continuous and, e.g., you can do `ptr_ + 2` to access the third element, + * - does not implement any "submdspan" methods (C++26), + * - `object[i,j,k,...]` is replaced by either `object(i,j,k,...)` or `object[{i,j,k,...}]`, as C++17 does not + * allow more than one argument for `operator[]`, + * - two additional methods: `update_extent` and `update_data` to avoid recalculating the helpers in the mapping class + * at each size modification of the underlying container, when the update is trivial (i.e. when only rank 0 is + * modified, which happens often in our use case). + */ +template +class Simple_mdspan +{ + public: + using layout_type = LayoutPolicy; + using mapping_type = typename LayoutPolicy::template mapping; + using extents_type = Extents; + using element_type = T; + using value_type = std::remove_cv_t; + using index_type = typename mapping_type::index_type; + using size_type = typename mapping_type::size_type; + using rank_type = typename mapping_type::rank_type; + using data_handle_type = T*; + using reference = T&; + + Simple_mdspan() : ptr_(nullptr) {} + + Simple_mdspan(const Simple_mdspan& rhs) = default; + Simple_mdspan(Simple_mdspan&& rhs) = default; + + template + explicit Simple_mdspan(data_handle_type ptr, IndexTypes... exts) + : ptr_(ptr), map_(extents_type(exts...)) + { + GUDHI_CHECK(ptr != nullptr || empty() || Extents::rank() == 0, "Given pointer is not properly initialized."); + } + + template + constexpr explicit Simple_mdspan(data_handle_type ptr, const std::array& exts) + : ptr_(ptr), map_(extents_type(exts)) + { + GUDHI_CHECK(ptr != nullptr || empty() || Extents::rank() == 0, "Given pointer is not properly initialized."); + } + + Simple_mdspan(data_handle_type ptr, const mapping_type& m) : ptr_(ptr), map_(m) {} + + Simple_mdspan& operator=(const Simple_mdspan& rhs) = default; + Simple_mdspan& operator=(Simple_mdspan&& rhs) = default; + + // version with [] not possible before C++23 + template + constexpr reference operator()(IndexTypes... indices) const + { + return operator[]({static_cast(indices)...}); + } + + template > + reference operator[](const IndexRange& indices) const + { + return *(ptr_ + map_(indices)); + } + + constexpr rank_type rank() noexcept { return map_.extents().rank(); } + + constexpr rank_type rank_dynamic() noexcept { return map_.extents().rank_dynamic(); } + + static constexpr std::size_t static_extent(rank_type r) noexcept { return std::numeric_limits::max(); } + + constexpr index_type extent(rank_type r) const + { + GUDHI_CHECK(r < map_.extents().rank(), "Out of bound index."); + return map_.extents().extent(r); + } + + constexpr size_type size() const noexcept { return map_.required_span_size(); } + + constexpr bool empty() const noexcept { return map_.required_span_size() == 0; } + + constexpr index_type stride(rank_type r) const { return map_.stride(r); } + + constexpr const extents_type& extents() const noexcept { return map_.extents(); } + + constexpr const data_handle_type& data_handle() const noexcept { return ptr_; } + + constexpr const mapping_type& mapping() const noexcept { return map_; } + + // if is_unique() is true for all possible instantiations of this class + static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); } + + // if is_exhaustive() is true for all possible instantiations of this class + static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); } + + // if is_strided() is true for all possible instantiations of this class + static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); } + + // unicity of the mapping (i,j,k,...) -> real index + constexpr bool is_unique() const { return map_.is_unique(); } + + // if all real indices have a preimage in form (i,j,k,...) + constexpr bool is_exhaustive() const { return map_.is_exhaustive(); } + + // if distance in memory is constant between two values in same rank + constexpr bool is_strided() const { return map_.is_strided(); } + + friend constexpr void swap(Simple_mdspan& x, Simple_mdspan& y) noexcept + { + std::swap(x.ptr_, y.ptr_); + swap(x.map_, y.map_); + } + + // as not everything is computed at compile time as for mdspan, update is usually faster than reconstructing + // everytime. + void update_extent(rank_type r, index_type new_value) { map_.update_extent(r, new_value); } + + // for update_extent to make sense, as resizing the vector can move it in the memory + void update_data(data_handle_type ptr) + { + GUDHI_CHECK(ptr != nullptr, "Null pointer not valid input."); + ptr_ = ptr; + } + + private: + data_handle_type ptr_; + mapping_type map_; +}; + +template +Simple_mdspan(CArray&) + -> Simple_mdspan, Gudhi::extents>>; + +template +Simple_mdspan(Pointer&&) + -> Simple_mdspan>, Gudhi::extents>; + +template +explicit Simple_mdspan(ElementType*, Integrals...) + -> Simple_mdspan>; + +template +Simple_mdspan(ElementType*, const std::array&) + -> Simple_mdspan>; + +template +Simple_mdspan(ElementType*, const Gudhi::extents&) + -> Simple_mdspan>; + +template +Simple_mdspan(ElementType*, const MappingType&) + -> Simple_mdspan; + +} // namespace Gudhi + +#endif // GUDHI_SIMPLE_MDSPAN_H_ \ No newline at end of file diff --git a/multipers/gudhi/gudhi/slicer_helpers.h b/multipers/gudhi/gudhi/slicer_helpers.h new file mode 100644 index 00000000..acf19525 --- /dev/null +++ b/multipers/gudhi/gudhi/slicer_helpers.h @@ -0,0 +1,779 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux, Hannah Schreiber + * + * Copyright (C) 2023-25 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file slicer_helpers.h + * @author David Loiseaux, Hannah Schreiber + * @brief Contains the helper methods @ref Gudhi::multi_persistence::build_complex_from_scc_file, + * @ref Gudhi::multi_persistence::write_complex_to_scc_file, @ref Gudhi::multi_persistence::build_slicer_from_scc_file, + * @ref Gudhi::multi_persistence::build_complex_from_bitmap and @ref Gudhi::multi_persistence::build_slicer_from_bitmap. + */ + +#ifndef MP_SLICER_HELPERS_H_ +#define MP_SLICER_HELPERS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef GUDHI_USE_TBB +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Gudhi { +namespace multi_persistence { + +/** + * @ingroup multi_persistence + * + * @brief Builds a complex for the scc format file given. Assumes that every index appearing in a boundary in the file + * corresponds to a real line in the file (for example, the lowest dimension has always empty boundaries). + * + * @tparam MultiFiltrationValue Filtration value class respecting the @ref MultiFiltrationValue concept. It will be + * used as filtration value type of the new complex. + * @param inFilePath Path to scc file. + * @param isRivetCompatible Set to true if the file is written such that Rivet can read it. See TODO ref. + * Default value: false. + * @param isReversed Set to true if the cells in the file are written in increasing dimension order instead of + * the standard decreasing order. Default value: false. + * @param shiftDimensions Indicates if there is a shift in the dimension written in the file: if the value is 0, it + * means that the smallest dimension is 0, if the value is positive, the smallest dimension is assumed to be + * `shiftDimensions` instead of 0, and if the value is negative, the `abs(shiftDimensions)` smallest dimensions in + * the file are ignored and the smallest remaining dimension is interpreted as 0. Default value: 0. + */ +template +inline Multi_parameter_filtered_complex build_complex_from_scc_file( + const std::string& inFilePath, + bool isRivetCompatible = false, + bool isReversed = false, + int shiftDimensions = 0) +{ + using Fil = MultiFiltrationValue; + using Complex = Multi_parameter_filtered_complex; + using Index = typename Complex::Index; + + std::string line; + std::ifstream file(inFilePath); + unsigned int numberOfParameters; + + if (!file.is_open()) { + // TODO: throw instead? + std::cerr << "Unable to open input file: " << inFilePath << '\n'; + file.setstate(std::ios::failbit); + return Complex(); + } + + auto error = [&file](const std::string& msg) { + file.close(); + throw std::invalid_argument(msg); + }; + auto is_comment_or_empty_line = [](const std::string& line) -> bool { + size_t current = line.find_first_not_of(' ', 0); + if (current == std::string::npos) return true; // is empty line + if (line[current] == '#') return true; // is comment + return false; + }; + + while (getline(file, line, '\n') && is_comment_or_empty_line(line)); + if (!file) error("Empty file!"); + + if (isRivetCompatible && line != "firep") error("Wrong file format. Should start with 'firep'."); + if (!isRivetCompatible && line != "scc2020") error("Wrong file format. Should start with 'scc2020'."); + + while (getline(file, line, '\n') && is_comment_or_empty_line(line)); + if (!file) error("Premature ending of the file. Stops before numbers of parameters."); + + if (isRivetCompatible) { + numberOfParameters = 2; + getline(file, line, '\n'); // second rivet label + } else { + std::size_t current = line.find_first_not_of(' ', 0); + std::size_t next = line.find_first_of(' ', current); + numberOfParameters = std::stoi(line.substr(current, next - current)); + } + + while (getline(file, line, '\n') && is_comment_or_empty_line(line)); + if (!file) error("Premature ending of the file. Not a single cell was specified."); + + std::vector counts; + Index numberOfCells = 0; + counts.reserve(line.size() + shiftDimensions); + std::size_t current = line.find_first_not_of(' ', 0); + if (shiftDimensions != 0 && isReversed && current != std::string::npos) { + if (shiftDimensions > 0) { + counts.resize(shiftDimensions, 0); + } else { + for (int i = shiftDimensions; i < 0 && current != std::string::npos; ++i) { + std::size_t next = line.find_first_of(' ', current); + current = line.find_first_not_of(' ', next); + } + } + } + while (current != std::string::npos) { + std::size_t next = line.find_first_of(' ', current); + counts.push_back(std::stoi(line.substr(current, next - current))); + numberOfCells += counts.back(); + current = line.find_first_not_of(' ', next); + } + if (shiftDimensions != 0 && !isReversed) { + counts.resize(counts.size() + shiftDimensions, 0); + } + + std::size_t dimIt = 0; + while (dimIt < counts.size() && counts[dimIt] == 0) ++dimIt; + + if (dimIt == counts.size()) return Complex(); + + std::size_t shift = isReversed ? 0 : counts[dimIt]; + unsigned int nextShift = isReversed ? 0 : counts.size() == 1 ? 0 : counts[dimIt + 1]; + unsigned int tmpNextShift = counts[dimIt]; + + auto get_boundary = [&isReversed, &numberOfCells]( + const std::string& line, std::size_t start, std::size_t shift) -> std::vector { + std::vector res; + res.reserve(line.size() - start); + std::size_t current = line.find_first_not_of(' ', start); + while (current != std::string::npos) { + std::size_t next = line.find_first_of(' ', current); + Index idx = std::stoi(line.substr(current, next - current)) + shift; + res.push_back(isReversed ? idx : numberOfCells - 1 - idx); + current = line.find_first_not_of(' ', next); + } + std::sort(res.begin(), res.end()); + return res; + }; + auto get_filtration_value = [numberOfParameters, &error](const std::string& line, std::size_t end) -> Fil { + std::vector res; + res.reserve(end); + bool isPlusInf = true; + bool isMinusInf = true; + std::size_t current = line.find_first_not_of(' ', 0); + while (current < end) { + std::size_t next = line.find_first_of(' ', current); + res.push_back(std::stod(line.substr(current, next - current))); + if (isPlusInf && res.back() != Fil::T_inf) isPlusInf = false; + if (isMinusInf && res.back() != Fil::T_m_inf) isMinusInf = false; + current = line.find_first_not_of(' ', next); + } + if (isPlusInf) return Fil::inf(numberOfParameters); + if (isMinusInf) return Fil::minus_inf(numberOfParameters); + if (res.size() % numberOfParameters != 0) error("Wrong format. The number of parameters does not match."); + return Fil(res.begin(), res.end(), numberOfParameters); + }; + + typename Complex::Boundary_container boundaries(numberOfCells); + typename Complex::Dimension_container dimensions(numberOfCells); + typename Complex::Filtration_value_container filtrationValues(numberOfCells); + std::size_t i = 0; + // because of possible negative dimension shifts, the document should not always be read to the end + // therefore `dimIt < counts.size()` is also a stop condition + while (getline(file, line, '\n') && dimIt < counts.size()) { + if (!is_comment_or_empty_line(line)) { + std::size_t sep = line.find_first_of(';', 0); + filtrationValues[i] = get_filtration_value(line, sep); + boundaries[i] = get_boundary(line, sep + 1, shift); + dimensions[i] = isReversed ? dimIt : counts.size() - 1 - dimIt; + + --counts[dimIt]; + while (dimIt < counts.size() && counts[dimIt] == 0) { + ++dimIt; + if (dimIt != counts.size()) { + shift += nextShift; + nextShift = isReversed ? tmpNextShift : dimIt < counts.size() - 1 ? counts[dimIt + 1] : 0; + tmpNextShift = counts[dimIt]; + } + } + ++i; + } + } + + if (!isReversed) { // to order by dimension + std::reverse(dimensions.begin(), dimensions.end()); + std::reverse(boundaries.begin(), boundaries.end()); + std::reverse(filtrationValues.begin(), filtrationValues.end()); + } + + file.close(); + + return Complex(std::move(boundaries), std::move(dimensions), std::move(filtrationValues)); +} + +/** + * @ingroup multi_persistence + * + * @brief Writes the given complex into a file with scc format. Assumes that every index appearing in a boundary of + * the complex corresponds to an existing index in the complex (for example, the lowest dimension has always empty + * boundaries). + * + * @tparam MultiFiltrationValue Filtration value of the given complex. + * @param outFilePath Path with file name into which to write. + * @param complex Complex to write. Every index appearing in a boundary of the complex has to correspond to an existing + * index in the complex + * @param degree TODO Default value: -1. + * @param rivetCompatible Set to true if the written file has to be Rivet compatible. Note that Rivet only accepts + * bi-filtrations. Default value: false. + * @param ignoreLastGenerators Set to true, if the generators with last dimension in the list should be ignored + * (maximal dimension by default, minimal dimension if `reverse` is true). Default value: false. + * @param stripComments Set to true, if no comment should be written in the file (comments are lines starting with `#` + * and which are ignored when read). Default value: false. + * @param reverse Set to true if the generators should be written in increasing order of dimension instead of + * decreasing. Default value: false. + */ +template +inline void write_complex_to_scc_file(const std::string& outFilePath, + const Multi_parameter_filtered_complex& complex, + int degree = -1, + bool rivetCompatible = false, + bool ignoreLastGenerators = false, + bool stripComments = false, + bool reverse = false) +{ + if (!complex.is_ordered_by_dimension()) { + // other solution would be to call build_permuted_complex ourself, but this is a good way to make the + // user aware of it. + throw std::invalid_argument( + "The given complex has to be ordered by dimension. If it is not the case, call this method with " + "`build_permuted_complex(complex).first` or `build_permuted_complex(complex, permutation_by_dim)` instead."); + return; + } + + unsigned int numberOfParameters = complex.get_number_of_parameters(); + + std::ofstream file(outFilePath); + + if (rivetCompatible) + file << "firep\n"; + else + file << "scc2020\n"; + + // TODO: change line for gudhi + if (!stripComments && !rivetCompatible) + file << "# This file was generated by multipers (https://github.com/DavidLapous/multipers).\n"; + + if (!stripComments && !rivetCompatible) file << "# Number of parameters\n"; + + if (rivetCompatible) { + GUDHI_CHECK(numberOfParameters == 2, "Rivet only handles bifiltrations."); + file << "Filtration 1\n"; + file << "Filtration 2\n"; + } else { + file << std::to_string(numberOfParameters) << "\n"; + } + + if (!stripComments) file << "# Sizes of generating sets\n"; + + using Fil = MultiFiltrationValue; + + int maxDim = complex.get_max_dimension(); + int minDim = maxDim; + const auto& dimensions = complex.get_dimensions(); + + std::vector > indicesByDim(maxDim + 1); + std::vector shiftedIndices(complex.get_number_of_cycle_generators()); + for (std::size_t i = 0; i < complex.get_number_of_cycle_generators(); ++i) { + auto dim = dimensions[i]; + minDim = dim < minDim ? dim : minDim; + auto& atDim = indicesByDim[reverse ? dim : maxDim - dim]; + shiftedIndices[i] = atDim.size(); + atDim.push_back(i); + } + if (degree < 0) degree = minDim; + int minIndex = reverse ? degree - 1 : 0; + int maxIndex = reverse ? maxDim : maxDim - degree + 1; + maxIndex = std::max(maxIndex, -1); + if (ignoreLastGenerators) maxIndex--; + if (rivetCompatible) minIndex = maxIndex - 2; + +#ifdef DEBUG_TRACES + std::cout << "minDim = " << minDim << " maxDim = " << maxDim << " minIndex = " << minIndex + << " maxIndex = " << maxIndex << " degree = " << degree << std::endl; +#endif + + auto print_fil_values = [&](const Fil& fil) { + GUDHI_CHECK(fil.num_parameters() == numberOfParameters, "Filtration value has wrong number of parameters."); + for (unsigned int g = 0; g < fil.num_generators(); ++g) { + for (unsigned int p = 0; p < fil.num_parameters(); ++p) { + file << fil(g, p) << " "; + } + } + }; + + if (minIndex < 0) file << 0 << " "; + for (int i = 0; i < minIndex; ++i) file << 0 << " "; + for (int i = std::max(minIndex, 0); i <= std::min(maxDim, maxIndex); ++i) { + file << indicesByDim[i].size() << " "; + } + if (!rivetCompatible) + for (int i = maxIndex + 1; i <= maxDim; ++i) file << 0 << " "; + if (maxIndex > maxDim) file << 0; + file << "\n"; + + file << std::setprecision(std::numeric_limits::digits); + + std::size_t startIndex = reverse ? minIndex + 1 : minIndex; + std::size_t endIndex = reverse ? maxIndex : maxIndex - 1; + const auto& filtValues = complex.get_filtration_values(); + const auto& boundaries = complex.get_boundaries(); + int currDim; + if (reverse) + currDim = minIndex == -1 ? 0 : minIndex; + else + currDim = maxIndex == maxDim + 1 ? maxDim + 1 : maxDim; + + if (reverse) { + if (!stripComments) file << "# Block of dimension " << currDim++ << "\n"; + if (minIndex >= 0) { + for (auto index : indicesByDim[minIndex]) { + print_fil_values(filtValues[index]); + file << ";\n"; + } + } + } + for (std::size_t i = startIndex; i <= endIndex; ++i) { + if (!stripComments) { + file << "# Block of dimension " << currDim << "\n"; + if (reverse) + ++currDim; + else + --currDim; + } + for (auto index : indicesByDim[i]) { + print_fil_values(filtValues[index]); + file << "; "; + for (auto b : boundaries[index]) file << shiftedIndices[b] << " "; + file << "\n"; + } + } + if (!reverse) { + if (!stripComments) file << "# Block of dimension " << currDim << "\n"; + if (maxIndex <= maxDim) { + for (auto index : indicesByDim[maxIndex]) { + print_fil_values(filtValues[index]); + file << ";\n"; + } + } + } +} + +/** + * @ingroup multi_persistence + * + * @brief Builds a complex from the given bitmap. The bitmap here is a grid where each node contains a 1-critical + * filtration value, which will be interpreted as a vertex in a cubical complex. The filtration values of the higher + * dimensional cells are deduced by taking at each parameter the maximal value of its facets at this parameter. + * + * Note that for the bitmap to represent a valid multi-parameter filtration, all filtration values have to have the + * same number of parameters. The behaviour is undefined otherwise. + * + * @tparam OneCriticalMultiFiltrationValue Filtration value class respecting the @ref MultiFiltrationValue concept. + * It will be used as filtration value type of the new complex. + * @param vertexValues Bitmap with 1-critical filtration values. Represented as a single vector, the next input + * parameter @p shape indicates the shape of the real bitmap. + * @param shape Shape of the bitmap. E.g., if @p shape is \f$ {3, 4} \f$, then the bitmap is a \f$ (4 x 3) \f$ grid + * with four lines and three columns. The vector @p vertexValues should then contain 12 elements: the three first + * elements will be read as the first line, the three next elements as the second line etc. until having 4 lines. + */ +template +inline Multi_parameter_filtered_complex build_complex_from_bitmap( + const std::vector& vertexValues, + const std::vector& shape) +{ + using Fil = OneCriticalMultiFiltrationValue; + using Complex = Multi_parameter_filtered_complex; + using Index = typename Complex::Index; + using Bitmap_cubical_complex_base = Gudhi::cubical_complex::Bitmap_cubical_complex_base; + using Bitmap_cubical_complex = Gudhi::cubical_complex::Bitmap_cubical_complex; + + if (shape.empty() || vertexValues.empty()) return Complex(); + + unsigned int numberOfParameters = vertexValues[0].num_parameters(); + + Bitmap_cubical_complex cub(shape, std::vector(vertexValues.size()), false); + + const unsigned int numberOfSimplices = cub.num_simplices(); + + typename Complex::Dimension_container dimensions(numberOfSimplices); + typename Complex::Boundary_container boundaries(numberOfSimplices); + unsigned int i = 0; + for (unsigned int d = 0; d < shape.size() + 1; ++d) { + for (auto sh : cub.skeleton_simplex_range(d)) { + cub.assign_key(sh, i); + dimensions[i] = d; + auto& col = boundaries[i]; + for (auto b : cub.boundary_simplex_range(sh)) col.push_back(cub.key(b)); + std::sort(col.begin(), col.end()); + ++i; + } + } + + auto get_vertices = [&boundaries](Index i) -> std::set { + auto rec_get_vertices = [&boundaries](const auto& self, Index i, std::set& vertices) -> void { + if (boundaries[i].empty()) { + vertices.insert(i); + return; + } + for (auto v : boundaries[i]) self(self, v, vertices); + }; + std::set vertices; + rec_get_vertices(rec_get_vertices, i, vertices); + return vertices; + }; + + typename Complex::Filtration_value_container filtrationValues(numberOfSimplices, Fil(numberOfParameters)); + + for (Index g = 0; g < numberOfSimplices; ++g) { + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) { + // should be faster than doing a proper `push_to_least_common_upper_bound` in the loop after + filtrationValues[g].force_generator_size_to_number_of_parameters(0); + } + for (auto v : get_vertices(g)) { + for (Index p = 0; p < numberOfParameters; ++p) { + // 1-critical + filtrationValues[g](0, p) = std::max(filtrationValues[g](0, p), vertexValues[v](0, p)); + } + } + } + + return Complex(std::move(boundaries), std::move(dimensions), std::move(filtrationValues)); +} + +/** + * @ingroup multi_persistence + * + * @brief Builds a complex from the given simplex tree. The complex will be ordered by dimension. + * + * @note The key values in the simplex tree nodes will be overwritten. + * + * @tparam MultiFiltrationValue Class following the @ref MultiFiltrationValue concept. + * @tparam SimplexTreeOptions Class following the @ref SimplexTreeOptions concept. Additionally, if + * `SimplexTreeOptions::Filtration_value` and `MultiFiltrationValue` are not the same type, there must + * be a method `as_type` taking `SimplexTreeOptions::Filtration_value` as argument and returning the value as an + * `MultiFiltrationValue` type. See @ref Gudhi::multi_filtration::as_type for implementations for + * @ref Gudhi::multi_filtration::Multi_parameter_filtration, + * @ref Gudhi::multi_filtration::Dynamic_multi_parameter_filtration and + * @ref Gudhi::multi_filtration::Degree_rips_bifiltration. + * @param simplexTree Simplex tree to convert. The key values of the simplex tree will be overwritten. + */ +template +inline Multi_parameter_filtered_complex build_complex_from_simplex_tree( + Simplex_tree& simplexTree) +{ + // declared here to enable custom `as_type` methods which are not in this namespace. + using namespace Gudhi::multi_filtration; + + // TODO: is_multi_filtration will discriminate all pre-made multi filtration classes, but not any user made + // class following the MultiFiltrationValue concept (as it was more thought for inner use). The tests should be + // re-thought or this one just removed. + static_assert(RangeTraits::is_multi_filtration, + "Target filtration value type has to correspond to the MultiFiltrationValue concept."); + + using Complex = Multi_parameter_filtered_complex; + + const unsigned int numberOfSimplices = simplexTree.num_simplices(); + + if (numberOfSimplices == 0) return Complex(); + + typename Complex::Dimension_container dimensions(numberOfSimplices); + typename Complex::Boundary_container boundaries(numberOfSimplices); + typename Complex::Filtration_value_container filtrationValues(numberOfSimplices); + + unsigned int i = 0; + // keys for boundaries have to be assigned first as we cannot use filtration_simplex_range to ensure that a face + // appears before its cofaces. + for (auto sh : simplexTree.complex_simplex_range()) { + simplexTree.assign_key(sh, i); + dimensions[i] = simplexTree.dimension(sh); + ++i; + } + + // Order simplices by dimension as an ordered Complex is more performant + std::vector newToOldIndex(numberOfSimplices); + std::vector oldToNewIndex(numberOfSimplices); + std::iota(newToOldIndex.begin(), newToOldIndex.end(), 0); + // stable sort to make the new complex more predicable and closer to a lexicographical sort in addition to dimension + std::stable_sort(newToOldIndex.begin(), newToOldIndex.end(), [&dimensions](unsigned int i, unsigned int j) { + return dimensions[i] < dimensions[j]; + }); + // Is there a way to directly get oldToNewIndex without constructing newToOldIndex? + for (unsigned int k = 0; k < numberOfSimplices; ++k) { + oldToNewIndex[newToOldIndex[k]] = k; + } + + for (auto sh : simplexTree.complex_simplex_range()) { + auto index = oldToNewIndex[simplexTree.key(sh)]; + dimensions[index] = simplexTree.dimension(sh); + if constexpr (std::is_same_v) { + filtrationValues[index] = simplexTree.filtration(sh); + } else { + filtrationValues[index] = as_type(simplexTree.filtration(sh)); + } + typename Complex::Boundary boundary(dimensions[index] == 0 ? 0 : dimensions[index] + 1); + unsigned int j = 0; + for (auto b : simplexTree.boundary_simplex_range(sh)) { + boundary[j] = oldToNewIndex[simplexTree.key(b)]; + ++j; + } + std::sort(boundary.begin(), boundary.end()); + boundaries[index] = std::move(boundary); + } + + return Complex(std::move(boundaries), std::move(dimensions), std::move(filtrationValues)); +} + +/** + * @ingroup multi_persistence + * + * @brief Builds a slicer for the scc format file given. Assumes that every index appearing in a boundary in the file + * corresponds to a real line in the file (for example, the lowest dimension has always empty boundaries). + * See @ref Slicer::write_slicer_to_scc_file "write_slicer_to_scc_file" to write a slicer into a scc format file. + * + * @tparam Slicer The @ref Slicer class with any valid template combination. + * @param inFilePath Path to scc file. + * @param isRivetCompatible Set to true if the file is written such that Rivet can read it. See TODO ref. + * Default value: false. + * @param isReversed Set to true if the cells in the file are written in increasing dimension order instead of + * the standard decreasing order. Default value: false. + * @param shiftDimensions Indicates if there is a shift in the dimension written in the file: if the value is 0, it + * means that the smallest dimension is 0, if the value is positive, the smallest dimension is assumed to be + * `shiftDimensions` instead of 0, and if the value is negative, the `abs(shiftDimensions)` smallest dimensions in + * the file are ignored and the smallest remaining dimension is interpreted as 0. Default value: 0. + */ +template +inline Slicer build_slicer_from_scc_file(const std::string& inFilePath, + bool isRivetCompatible = false, + bool isReversed = false, + int shiftDimensions = 0) +{ + auto cpx = build_complex_from_scc_file( + inFilePath, isRivetCompatible, isReversed, shiftDimensions); + return Slicer(std::move(cpx)); +} + +/** + * @ingroup multi_persistence + * + * @brief Builds a slicer from the given bitmap. The bitmap here is a grid where each node contains a 1-critical + * filtration value, which will be interpreted as a vertex in a cubical complex. The filtration values of the higher + * dimensional cells are deduced by taking at each parameter the maximal value of its facets at this parameter. + * + * Note that for the bitmap to represent a valid multi-parameter filtration, all filtration values have to have the + * same number of parameters. The behaviour is undefined otherwise. + * + * @tparam Slicer The @ref Slicer class with any valid template combination. + * @param vertexValues Bitmap with 1-critical filtration values. Represented as a single vector, the next input + * parameter @p shape indicates the shape of the real bitmap. + * @param shape Shape of the bitmap. E.g., if @p shape is \f$ {3, 4} \f$, then the bitmap is a \f$ (4 x 3) \f$ grid + * with four lines and three columns. The vector @p vertexValues should then contain 12 elements: the three first + * elements will be read as the first line, the three next elements as the second line etc. until having 4 lines. + */ +template +inline Slicer build_slicer_from_bitmap(const std::vector& vertexValues, + const std::vector& shape) +{ + auto cpx = build_complex_from_bitmap(vertexValues, shape); + return Slicer(std::move(cpx)); +} + +/** + * @ingroup multi_persistence + * + * @brief Builds a slicer from the given simplex tree. The inner complex will be ordered by dimension. + * + * @tparam Slicer The @ref Slicer class with any valid template combination. + * @tparam SimplexTreeOptions Class following the @ref SimplexTreeOptions concept such that + * @ref SimplexTreeOptions::Filtration_value follows the @ref MultiFiltrationValue concept. + * @param simplexTree Simplex tree to convert. + */ +template +inline Slicer build_slicer_from_simplex_tree(Simplex_tree& simplexTree) +{ + auto cpx = build_complex_from_simplex_tree(simplexTree); + return Slicer(std::move(cpx)); +} + +/** + * @private + */ +template +std::vector> +persistence_on_slices_(Slicer& slicer, F&& ini_slicer, unsigned int size, [[maybe_unused]] bool ignoreInf = true) +{ + using Barcode = typename Slicer::template Multi_dimensional_flat_barcode; + + if (size == 0) return {}; + + std::vector out(size); + + if constexpr (Slicer::Persistence::is_vine) { + std::forward(ini_slicer)(slicer, 0); + slicer.initialize_persistence_computation(false); + out[0] = slicer.template get_flat_barcode(); + for (auto i = 1U; i < size; ++i) { + std::forward(ini_slicer)(slicer, i); + slicer.vineyard_update(); + out[i] = slicer.template get_flat_barcode(); + } + } else { +#ifdef GUDHI_USE_TBB + using Index = typename Slicer::Index; + tbb::enumerable_thread_specific threadLocals(slicer.weak_copy()); + tbb::parallel_for(static_cast(0), size, [&](const Index& i) { + typename Slicer::Thread_safe& s = threadLocals.local(); + std::forward(ini_slicer)(s, i); + s.initialize_persistence_computation(ignoreInf); + out[i] = s.template get_flat_barcode(); + }); +#else + for (auto i = 0U; i < size; ++i) { + std::forward(ini_slicer)(slicer, i); + slicer.initialize_persistence_computation(ignoreInf); + out[i] = slicer.template get_flat_barcode(); + } +#endif + } + + return out; +} + +/** + * @ingroup multi_persistence + * + * @brief Returns the barcodes of all the given lines. A line is represented as a pair with the first element being + * a point on the line and the second element a vector giving the positive direction of the line. The direction + * container can be empty: then the slope is assumed to be 1. + * + * @tparam Slicer Either @ref Slicer or @ref Thread_safe_slicer class with any valid template combination. + * @tparam T Type of a coordinate element. + * @tparam U Type of filtration values in the output barcode. Default value: T. + * @tparam idx If true, the complex indices instead of the actual filtration values are used for the bars. It is + * recommended to use an integer type for `U` in that case. Default value: false. + * @param slicer Slicer from which to compute persistence. + * @param basePoints Vector of base points for the lines. The dimension of a point has to correspond to the number + * of parameters in the slicer. + * @param directions Vector of directions for the lines. A direction has to have the same dimension than a point. + * Can be empty, then the slope is assumed to be 1. + * @param ignoreInf If true, all cells at infinity filtration values are ignored when computing, resulting + * potentially in less storage use and better performance. But the parameter will be ignored if + * PersistenceAlgorithm::is_vine is true. + */ +template +std::vector> persistence_on_slices( + Slicer& slicer, + const std::vector>& basePoints, + const std::vector>& directions, + bool ignoreInf = true) +{ + GUDHI_CHECK(directions.empty() || directions.size() == basePoints.size(), + "There should be as many directions than base points."); + GUDHI_CHECK(basePoints.empty() || basePoints[0].size() == slicer.get_number_of_parameters(), + "There should be as many directions than base points."); + + std::vector dummy; + auto get_direction = [&](unsigned int i) -> const std::vector& { + return directions.empty() ? dummy : directions[i]; + }; + + return persistence_on_slices_( + slicer, + [&](auto& s, unsigned int i) { s.push_to(Line(basePoints[i], get_direction(i))); }, + basePoints.size(), + ignoreInf); +} + +/** + * @ingroup multi_persistence + * + * @brief Returns the barcodes of all the given slices. + * + * @tparam Slicer Either @ref Slicer or @ref Thread_safe_slicer class with any valid template combination. + * @tparam T Type of a slice element. + * @tparam U Type of filtration values in the output barcode. Default value: T. + * @tparam idx If true, the complex indices instead of the actual filtration values are used for the bars. It is + * recommended to use an integer type for `U` in that case. Default value: false. + * @param slicer Slicer from which to compute persistence. + * @param slices Vector of slices. A slice has to has as many elements than cells in the slicer. + * @param ignoreInf If true, all cells at infinity filtration values are ignored when computing, resulting + * potentially in less storage use and better performance. But the parameter will be ignored if + * PersistenceAlgorithm::is_vine is true. + */ +template +std::vector> +persistence_on_slices(Slicer& slicer, const std::vector>& slices, bool ignoreInf = true) +{ + GUDHI_CHECK(slices.empty() || slices[0].size() == slicer.get_number_of_cycle_generators(), + "There should be as many elements in a slice than cells in the slicer."); + + return persistence_on_slices_( + slicer, [&](auto& s, unsigned int i) { s.set_slice(slices[i]); }, slices.size(), ignoreInf); +} + +// Mostly for python +/** + * @ingroup multi_persistence + * + * @brief Returns the barcodes of all the given slices. + * + * @tparam Slicer Either @ref Slicer or @ref Thread_safe_slicer class with any valid template combination. + * @tparam T Type of a slice element. + * @tparam U Type of filtration values in the output barcode. Default value: T. + * @tparam idx If true, the complex indices instead of the actual filtration values are used for the bars. It is + * recommended to use an integer type for `U` in that case. Default value: false. + * @param slicer Slicer from which to compute persistence. + * @param slices Pointer to the begining of slices continuously aligned after another in the memory. + * @param numberOfSlices Number of slices represented by the pointer. + * @param ignoreInf If true, all cells at infinity filtration values are ignored when computing, resulting + * potentially in less storage use and better performance. But the parameter will be ignored if + * PersistenceAlgorithm::is_vine is true. + */ +template >> +std::vector> +persistence_on_slices(Slicer& slicer, T* slices, unsigned int numberOfSlices, bool ignoreInf = true) +{ + auto num_gen = slicer.get_number_of_cycle_generators(); + auto view = Gudhi::Simple_mdspan(slices, numberOfSlices, num_gen); + + return persistence_on_slices_( + slicer, + [&](auto& s, unsigned int i) { + T* start = &view(i, 0); + auto r = boost::iterator_range(start, start + num_gen); + s.set_slice(r); + }, + numberOfSlices, + ignoreInf); +} + +} // namespace multi_persistence +} // namespace Gudhi + +#endif // MP_SLICER_HELPERS_H_ diff --git a/multipers/gudhi/mma_interface_coh.h b/multipers/gudhi/mma_interface_coh.h deleted file mode 100644 index 970a2bc5..00000000 --- a/multipers/gudhi/mma_interface_coh.h +++ /dev/null @@ -1,256 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which - * is released under MIT. See file LICENSE or go to - * https://gudhi.inria.fr/licensing/ for full license details. Author(s): Hannah - * Schreiber - * - * Copyright (C) 2024 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ -/** - * @file options.h - * @author Hannah Schreiber - * @brief Interface of the matrix for MMA - */ - -#ifndef MMA_INTERFACE_COH_H -#define MMA_INTERFACE_COH_H - -#include -#include -#include -#include -#include - -#include -#include - -namespace Gudhi { -namespace multiparameter { -namespace truc_interface { - -template -class Boundary_matrix_as_filtered_complex_for_coh { - public: - using Simplex_key = std::uint32_t; - using Simplex_handle = Simplex_key; - using Filtration_value = Simplex_key; - using Dimension = int; - - class Filtration_range_iterator - : public boost::iterator_facade { - public: - Filtration_range_iterator(const std::vector *new_to_old_perm = nullptr) - : new_to_old_perm_(new_to_old_perm), currPos_(0) { - if (new_to_old_perm_ != nullptr) { - find_next_valid_index(); - } - } - - private: - // mandatory for the boost::iterator_facade inheritance. - friend class boost::iterator_core_access; - - const std::vector *new_to_old_perm_; - std::size_t currPos_; - - bool equal(Filtration_range_iterator const &other) const { - return new_to_old_perm_ == other.new_to_old_perm_ && currPos_ == other.currPos_; - } - - const Simplex_handle &dereference() const { return (*new_to_old_perm_)[currPos_]; } - - void increment() { - ++currPos_; - find_next_valid_index(); - } - - void set_to_end() { - new_to_old_perm_ = nullptr; - currPos_ = 0; - } - - void find_next_valid_index() { - while (currPos_ < new_to_old_perm_->size() && dereference() >= new_to_old_perm_->size()) ++currPos_; - if (currPos_ == new_to_old_perm_->size()) set_to_end(); - } - }; - - class Filtration_simplex_range { - public: - using iterator = Filtration_range_iterator; - - Filtration_simplex_range(std::vector const *new_to_old_perm) - : new_to_old_perm_(new_to_old_perm), size_(-1) {} - - iterator begin() const { return iterator(new_to_old_perm_); } - - iterator end() const { return iterator(); } - - std::size_t size() { - if (size_ == static_cast(-1)) { - size_ = 0; - for (auto i : *new_to_old_perm_) { - if (i < new_to_old_perm_->size()) ++size_; - } - } - return size_; - } - - inline friend std::ostream &operator<<(std::ostream &stream, const Filtration_simplex_range &range) { - stream << "Filtration_simplex_range: "; - for (const auto idx : range) { - stream << idx << " "; - } - stream << "\n"; - return stream; - } - - private: - std::vector const *new_to_old_perm_; - std::size_t size_; - }; - - Boundary_matrix_as_filtered_complex_for_coh() : boundaries_(nullptr), new_to_old_perm_(nullptr) {} - - Boundary_matrix_as_filtered_complex_for_coh(const Structure &boundaries, - const std::vector &permutation) - : boundaries_(&boundaries), - new_to_old_perm_(&permutation), - keys_(permutation.size(), -1), - keysToSH_(permutation.size(), -1) { - assert(permutation.size() == boundaries.size()); - } - - friend void swap(Boundary_matrix_as_filtered_complex_for_coh &be1, Boundary_matrix_as_filtered_complex_for_coh &be2) { - std::swap(be1.boundaries_, be2.boundaries_); - std::swap(be1.new_to_old_perm_, be2.new_to_old_perm_); - be1.keys_.swap(be2.keys_); - be1.keysToSH_.swap(be2.keysToSH_); - } - - std::size_t num_simplices() const { return boundaries_->size(); } - - Filtration_value filtration(Simplex_handle sh) const { - return sh == null_simplex() ? std::numeric_limits::max() : keys_[sh]; - } - - Dimension dimension() const { return boundaries_->max_dimension(); } - - Dimension dimension(Simplex_handle sh) const { return sh == null_simplex() ? -1 : boundaries_->dimension(sh); } - - void assign_key(Simplex_handle sh, Simplex_key key) { - if (sh != null_simplex()) keys_[sh] = key; - if (key != null_key()) keysToSH_[key] = sh; - } - - Simplex_key key(Simplex_handle sh) const { return sh == null_simplex() ? null_key() : keys_[sh]; } - - static constexpr Simplex_key null_key() { return static_cast(-1); } - - Simplex_handle simplex(Simplex_key key) const { return key == null_key() ? null_simplex() : keysToSH_[key]; } - - static constexpr Simplex_handle null_simplex() { return static_cast(-1); } - - std::pair endpoints(Simplex_handle sh) const { - if (sh == null_simplex()) return {null_simplex(), null_simplex()}; - assert(dimension(sh) == 1); - const auto &col = (*boundaries_)[sh]; - assert(col.size() == 2); - return {col[0], col[1]}; - } - - Filtration_simplex_range filtration_simplex_range() const { return Filtration_simplex_range(new_to_old_perm_); } - - const std::vector &boundary_simplex_range(Simplex_handle sh) const { return (*boundaries_)[sh]; } - - void set_permutation(const std::vector &permutation) { new_to_old_perm_ = &permutation; } - - friend std::ostream &operator<<(std::ostream &stream, const Boundary_matrix_as_filtered_complex_for_coh &structure) { - std::vector inv(structure.new_to_old_perm_->size()); - for (unsigned int i = 0; i < structure.new_to_old_perm_->size(); ++i) { - inv[(*structure.new_to_old_perm_)[i]] = i; - } - - stream << "[\n"; - for (auto i : structure.filtration_simplex_range()) { - stream << "["; - for (const auto &stuff : structure.boundary_simplex_range(i)) stream << inv[stuff] << ", "; - stream << "]\n"; - } - - stream << "]\n"; - return stream; - } - - private: - Structure const *boundaries_; - std::vector const *new_to_old_perm_; - std::vector keys_; - std::vector keysToSH_; -}; - -template -class Persistence_backend_cohomology { - public: - using MatrixComplex = Boundary_matrix_as_filtered_complex_for_coh; - using pos_index = typename MatrixComplex::Simplex_key; - using dimension_type = typename MatrixComplex::Dimension; - using bar = Gudhi::persistence_matrix::Persistence_interval; - using Barcode = std::vector; - using Field_Zp = Gudhi::persistent_cohomology::Field_Zp; - using Persistent_cohomology = Gudhi::persistent_cohomology::Persistent_cohomology; - static const bool is_vine = false; - using Index = typename MatrixComplex::Simplex_handle; - using cycle_type = void; - - Persistence_backend_cohomology() {}; - - Persistence_backend_cohomology(const Boundary_matrix_type &boundaries, std::vector &permutation) - : matrix_(boundaries, permutation) {} - - friend void swap(Persistence_backend_cohomology &be1, Persistence_backend_cohomology &be2) { - swap(be1.matrix_, be2.matrix_); - } - - dimension_type get_dimension(pos_index i) { return matrix_.dimension(matrix_.simplex(i)); } - - Barcode get_barcode() { - Persistent_cohomology pcoh(matrix_, true); - pcoh.init_coefficients(2); - pcoh.compute_persistent_cohomology(0, false); - - const auto &pairs = pcoh.get_persistent_pairs(); - Barcode barcode(pairs.size()); - - unsigned int i = 0; - for (const auto &p : pairs) { - auto &b = barcode[i]; - b.dim = matrix_.dimension(get<0>(p)); - b.birth = get<0>(p); - b.death = get<1>(p); - ++i; - } - - return barcode; - } - - std::size_t size() { return matrix_.num_simplices(); } - - friend std::ostream &operator<<(std::ostream &stream, Persistence_backend_cohomology &structure) { - stream << structure.matrix_ << "\n"; - return stream; - } - - void _update_permutation_ptr(std::vector &perm) { matrix_.set_permutation(perm); } - - private: - MatrixComplex matrix_; -}; - -} // namespace truc_interface -} // namespace multiparameter -} // namespace Gudhi - -#endif // MMA_INTERFACE_COH_H diff --git a/multipers/gudhi/mma_interface_matrix.h b/multipers/gudhi/mma_interface_matrix.h deleted file mode 100644 index cf0ac617..00000000 --- a/multipers/gudhi/mma_interface_matrix.h +++ /dev/null @@ -1,293 +0,0 @@ -/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which - * is released under MIT. See file LICENSE or go to - * https://gudhi.inria.fr/licensing/ for full license details. Author(s): Hannah - * Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ -/** - * @file options.h - * @author Hannah Schreiber - * @brief Interface of the matrix for MMA - */ - -#ifndef MMA_INTERFACE_MATRIX_H -#define MMA_INTERFACE_MATRIX_H - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Gudhi { -namespace multiparameter { -namespace truc_interface { - -template -struct Multi_persistence_options : Gudhi::persistence_matrix::Default_options { - using Index = std::uint32_t; - static const bool has_matrix_maximal_dimension_access = false; - static const bool has_column_pairings = true; - static const bool has_vine_update = true; - static const bool can_retrieve_representative_cycles = true; -}; - -template -struct Multi_persistence_Clement_options : Gudhi::persistence_matrix::Default_options { - using Index = std::uint32_t; - static const bool has_matrix_maximal_dimension_access = false; - static const bool has_column_pairings = true; - static const bool has_vine_update = true; - static const bool is_of_boundary_type = false; - static const Gudhi::persistence_matrix::Column_indexation_types column_indexation_type = - Gudhi::persistence_matrix::Column_indexation_types::POSITION; - static const bool can_retrieve_representative_cycles = true; -}; - -template -struct No_vine_multi_persistence_options : Gudhi::persistence_matrix::Default_options { - using Index = std::uint32_t; - static const bool has_matrix_maximal_dimension_access = false; - static const bool has_column_pairings = true; - static const bool has_vine_update = false; -}; - -template -struct fix_presentation_options : Gudhi::persistence_matrix::Default_options { - using Index = std::uint32_t; - static const bool has_row_access = row_access; - static const bool has_map_column_container = false; - static const bool has_removable_columns = false; // WARN : idx will change if map is not true -}; - -template -class Persistence_backend_matrix { - public: - using matrix_type = Gudhi::persistence_matrix::Matrix; - using options = Matrix_options; - using cycle_type = typename matrix_type::Cycle; - static const bool is_vine = Matrix_options::has_vine_update; - - using bar = typename matrix_type::Bar; - // using index = typename matrix_type::index; - // using id_index = typename matrix_type::id_index; - using pos_index = typename matrix_type::Pos_index; - using Index = typename matrix_type::Index; - using dimension_type = typename matrix_type::Dimension; - - class Barcode_iterator : public boost::iterator_facade { - public: - Barcode_iterator(const typename matrix_type::Barcode *barcode, const std::vector *inv) - : barcode_(barcode->size() == 0 ? nullptr : barcode), perm_(barcode->size() == 0 ? nullptr : inv), currPos_(0) { - if (barcode_ != nullptr && perm_ != nullptr) { - auto &b = barcode_->operator[](currPos_); - currBar_.dim = b.dim; - currBar_.birth = perm_->operator[](b.birth); - currBar_.death = b.death == static_cast(-1) ? -1 : perm_->operator[](b.death); - } - } - - Barcode_iterator() : barcode_(nullptr), perm_(nullptr), currPos_(0) {} - - private: - // mandatory for the boost::iterator_facade inheritance. - friend class boost::iterator_core_access; - - const typename matrix_type::Barcode *barcode_; - const std::vector *perm_; - std::size_t currPos_; - bar currBar_; - - bool equal(Barcode_iterator const &other) const { - return barcode_ == other.barcode_ && perm_ == other.perm_ && currPos_ == other.currPos_; - } - - const bar &dereference() const { return currBar_; } - - void increment() { - constexpr const bool debug = false; - ++currPos_; - if (currPos_ == barcode_->size()) { - barcode_ = nullptr; - perm_ = nullptr; - currPos_ = 0; - } else { - auto &b = barcode_->operator[](currPos_); - currBar_.dim = b.dim; - currBar_.birth = perm_->operator[](b.birth); - if (debug && currBar_.birth > std::numeric_limits::max() / 2) { - std::cout << currBar_ << std::endl; - std::cout << "while " << b.birth; - std::cout << " " << perm_->size(); - } - currBar_.death = b.death == static_cast(-1) ? -1 : perm_->operator[](b.death); - } - } - }; - - class Barcode { - public: - using iterator = Barcode_iterator; - - Barcode(matrix_type &matrix, const std::vector *perm) : barcode_(&matrix.get_current_barcode()) { - const bool debug = false; - if constexpr (Matrix_options::has_vine_update) { - perm_ = perm; - } else { - perm_.reserve(perm->size()); - for (const auto &stuff : *perm) - if (stuff < perm->size()) // number of generators - perm_.push_back(stuff); - } - if constexpr (debug) { - std::cout << "Built matrix of size " << matrix.get_number_of_columns() << " / " << perm->size() << std::endl; - } - } - - iterator begin() const { - if constexpr (Matrix_options::has_vine_update) { - return Barcode_iterator(barcode_, perm_); - } else { - return Barcode_iterator(barcode_, &this->perm_); - } - } - - iterator end() const { return Barcode_iterator(); } - - /* using bar = typename matrix_type::Bar; */ - /* const bar& operator[](std::size_t i){ */ - /* return barcode_->at(); */ - /* } */ - std::size_t size() const { return barcode_->size(); } - - inline friend std::ostream &operator<<(std::ostream &stream, Barcode &structure) { - stream << "Barcode: " << structure.size() << "\n"; - for (const auto bar : structure) { - stream << "[" << bar.dim << "] "; - stream << bar.birth << ", " << bar.death; - stream << "\n"; - } - stream << "\n"; - return stream; - } - - private: - const typename matrix_type::Barcode *barcode_; - typename std::conditional *, std::vector>::type - perm_; - }; - - Persistence_backend_matrix() : permutation_(nullptr) {}; - - Persistence_backend_matrix(const Boundary_matrix_type &boundaries, std::vector &permutation) - : matrix_(boundaries.size()), permutation_(&permutation) { - static_assert(Matrix_options::is_of_boundary_type || Matrix_options::has_vine_update, "Clement implies vine."); - constexpr const bool verbose = false; - if constexpr (verbose) std::cout << "Constructing matrix..." << std::endl; - std::vector permutationInv(permutation_->size()); - std::vector boundary_container; - std::size_t c = 0; - for (auto i : *permutation_) { - if (i >= boundaries.size()) { - c++; - continue; - } - permutationInv[i] = c++; - boundary_container.resize(boundaries[i].size()); - if constexpr (verbose) - std::cout << i << "/" << permutation_->size() << " c= " << c << " dimension " << boundaries.dimension(i) - << "..." << std::endl - << std::flush; - for (std::size_t j = 0; j < boundaries[i].size(); ++j) { - boundary_container[j] = permutationInv[boundaries[i][j]]; - } - std::sort(boundary_container.begin(), boundary_container.end()); - matrix_.insert_boundary(c - 1, boundary_container, boundaries.dimension(i)); - } - } - - Persistence_backend_matrix(const Persistence_backend_matrix &toCopy) - : matrix_(toCopy.matrix_), permutation_(toCopy.permutation_) {} - - Persistence_backend_matrix(Persistence_backend_matrix &&other) noexcept - : matrix_(std::move(other.matrix_)), permutation_(std::exchange(other.permutation_, nullptr)) {} - - Persistence_backend_matrix &operator=(Persistence_backend_matrix other) { - swap(matrix_, other.matrix_); - std::swap(permutation_, other.permutation_); - return *this; - } - - friend void swap(Persistence_backend_matrix &be1, Persistence_backend_matrix &be2) { - swap(be1.matrix_, be2.matrix_); - std::swap(be1.permutation_, be2.permutation_); - } - - inline dimension_type get_dimension(pos_index i) { return matrix_.get_column_dimension(i); } - - inline void vine_swap(pos_index i) { matrix_.vine_swap(i); } - - inline Barcode get_barcode() { return Barcode(matrix_, permutation_); } - - inline std::size_t size() const { return this->matrix_.get_number_of_columns(); } - - inline friend std::ostream &operator<<(std::ostream &stream, Persistence_backend_matrix &structure) { - stream << "[\n"; - for (auto i = 0u; i < structure.matrix_.get_number_of_columns(); i++) { - stream << "["; - for (const auto &stuff : structure.matrix_.get_column(i)) stream << stuff << ", "; - stream << "]\n"; - } - - stream << "]\n"; - return stream; - } - - inline std::vector>> get_representative_cycles(bool update, - bool detailed = false) { - // Only used when vineyard, so shrunk permutation i.e. - // without the -1, is permutation as we keep inf values (they can become - // finite) cf barcode perm which is copied to remove the -1 - std::vector permutation2; - permutation2.reserve(permutation_->size()); - for (auto i : *permutation_) { - if (i >= matrix_.get_number_of_columns()) { - continue; - } - permutation2.push_back(i); - } - constexpr const bool verbose = false; - if (update) [[likely]] - matrix_.update_representative_cycles(); - std::vector>> current_cycles = - matrix_.get_representative_cycles_as_borders(detailed); - for (auto &cycle : current_cycles) { - if constexpr (verbose) std::cout << "Cycle (matrix_ order): "; - for (auto &border : cycle) { - for (auto &b : border) { - b = permutation2[b]; - } - } - } - return current_cycles; - } - - inline void _update_permutation_ptr(std::vector &perm) { permutation_ = &perm; } - - private: - matrix_type matrix_; - std::vector *permutation_; -}; - -} // namespace truc_interface -} // namespace multiparameter -} // namespace Gudhi -#endif // MMA_INTERFACE_MATRIX_H diff --git a/multipers/gudhi/scc_io.h b/multipers/gudhi/scc_io.h deleted file mode 100644 index bd9abd5f..00000000 --- a/multipers/gudhi/scc_io.h +++ /dev/null @@ -1,310 +0,0 @@ -#ifndef MULTIPERS_SCC_IO_H -#define MULTIPERS_SCC_IO_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -template -inline Slicer read_scc_file(const std::string& inFilePath, - bool isRivetCompatible = false, - bool isReversed = false, - int shiftDimensions = 0) { - using Filtration_value = typename Slicer::Filtration_value; - - std::string line; - std::ifstream file(inFilePath); - Slicer slicer; - unsigned int numberOfParameters; - - if (file.is_open()) { - auto error = [&file](std::string msg) { - file.close(); - throw std::invalid_argument(msg); - }; - auto is_comment_or_empty_line = [](const std::string& line) -> bool { - size_t current = line.find_first_not_of(' ', 0); - if (current == std::string::npos) return true; // is empty line - if (line[current] == '#') return true; // is comment - return false; - }; - - while (getline(file, line, '\n') && is_comment_or_empty_line(line)); - if (!file) error("Empty file!"); - - if (isRivetCompatible && line.compare("firep") != 0) error("Wrong file format. Should start with 'firep'."); - if (!isRivetCompatible && line.compare("scc2020") != 0) error("Wrong file format. Should start with 'scc2020'."); - - while (getline(file, line, '\n') && is_comment_or_empty_line(line)); - if (!file) error("Premature ending of the file. Stops before numbers of parameters."); - - if (isRivetCompatible) { - numberOfParameters = 2; - getline(file, line, '\n'); // second rivet label - } else { - std::size_t current = line.find_first_not_of(' ', 0); - std::size_t next = line.find_first_of(' ', current); - numberOfParameters = std::stoi(line.substr(current, next - current)); - } - - while (getline(file, line, '\n') && is_comment_or_empty_line(line)); - if (!file) error("Premature ending of the file. Not a single cell was specified."); - - std::vector counts; - unsigned int numberOfCells = 0; - counts.reserve(line.size() + shiftDimensions); - std::size_t current = line.find_first_not_of(' ', 0); - if (shiftDimensions != 0 && isReversed && current != std::string::npos) { - if (shiftDimensions > 0) { - counts.resize(shiftDimensions, 0); - } else { - for (int i = shiftDimensions; i < 0 && current != std::string::npos; ++i) { - std::size_t next = line.find_first_of(' ', current); - current = line.find_first_not_of(' ', next); - } - } - } - while (current != std::string::npos) { - std::size_t next = line.find_first_of(' ', current); - counts.push_back(std::stoi(line.substr(current, next - current))); - numberOfCells += counts.back(); - current = line.find_first_not_of(' ', next); - } - if (shiftDimensions != 0 && !isReversed) { - counts.resize(counts.size() + shiftDimensions, 0); - } - - std::size_t dimIt = 0; - while (dimIt < counts.size() && counts[dimIt] == 0) ++dimIt; - - if (dimIt == counts.size()) return slicer; - - std::size_t shift = isReversed ? 0 : counts[dimIt]; - unsigned int nextShift = isReversed ? 0 : counts.size() == 1 ? 0 : counts[dimIt + 1]; - unsigned int tmpNextShift = counts[dimIt]; - - auto get_boundary = [&isReversed, &numberOfCells](const std::string& line, - std::size_t start, - std::size_t shift) -> std::vector { - std::vector res; - res.reserve(line.size() - start); - std::size_t current = line.find_first_not_of(' ', start); - while (current != std::string::npos) { - std::size_t next = line.find_first_of(' ', current); - unsigned int idx = std::stoi(line.substr(current, next - current)) + shift; - res.push_back(isReversed ? idx : numberOfCells - 1 - idx); - current = line.find_first_not_of(' ', next); - } - std::sort(res.begin(), res.end()); - return res; - }; - auto get_filtration_value = [](const std::string& line, std::size_t end) -> Filtration_value { - Filtration_value res(0); - res.reserve(end); - bool isPlusInf = true; - bool isMinusInf = true; - std::size_t current = line.find_first_not_of(' ', 0); - while (current < end) { - std::size_t next = line.find_first_of(' ', current); - res.push_back(std::stod(line.substr(current, next - current))); - if (isPlusInf && res.back() != Filtration_value::T_inf) isPlusInf = false; - if (isMinusInf && res.back() != -Filtration_value::T_inf) isMinusInf = false; - current = line.find_first_not_of(' ', next); - } - if (isPlusInf) res = Filtration_value::inf(); - if (isMinusInf) res = Filtration_value::minus_inf(); - return res; - }; - - std::vector > generator_maps(numberOfCells); - std::vector generator_dimensions(numberOfCells); - std::vector generator_filtrations(numberOfCells); - std::size_t i = 0; - // because of possible negative dimension shifts, the document should not always be read to the end - // therefore `dimIt < counts.size()` is also a stop condition - while (getline(file, line, '\n') && dimIt < counts.size()) { - if (!is_comment_or_empty_line(line)) { - std::size_t sep = line.find_first_of(';', 0); - generator_filtrations[i] = get_filtration_value(line, sep); - if (generator_filtrations[i].is_finite() && generator_filtrations[i].num_parameters() != numberOfParameters) - error("Wrong format. The number of parameters does not match."); - generator_maps[i] = get_boundary(line, sep + 1, shift); - generator_dimensions[i] = isReversed ? dimIt : counts.size() - 1 - dimIt; - - --counts[dimIt]; - while (dimIt < counts.size() && counts[dimIt] == 0) { - ++dimIt; - if (dimIt != counts.size()) { - shift += nextShift; - nextShift = isReversed ? tmpNextShift : dimIt < counts.size() - 1 ? counts[dimIt + 1] : 0; - tmpNextShift = counts[dimIt]; - } - } - ++i; - } - } - - if (!isReversed) { // to order by dimension - std::reverse(generator_dimensions.begin(), generator_dimensions.end()); - std::reverse(generator_maps.begin(), generator_maps.end()); - std::reverse(generator_filtrations.begin(), generator_filtrations.end()); - } - - slicer = Slicer(generator_maps, generator_dimensions, generator_filtrations); - - file.close(); - } else { - std::cerr << "Unable to open input file: " << inFilePath << std::endl; - file.setstate(std::ios::failbit); - } - - return slicer; -}; - -template -inline void write_scc_file(const std::string& outFilePath, - const Slicer& slicer, - int numberOfParameters = -1, - int degree = -1, - bool rivetCompatible = false, - bool IgnoreLastGenerators = false, - bool stripComments = false, - bool reverse = false) { - constexpr bool verbose = false; - if (numberOfParameters < 0) { - numberOfParameters = slicer.num_parameters(); - } - assert(numberOfParameters > 0 && "Invalid number of parameters!"); - - std::ofstream file(outFilePath); - - if (rivetCompatible) - file << "firep\n"; - else - file << "scc2020\n"; - - if (!stripComments && !rivetCompatible) - file << "# This file was generated by multipers (https://github.com/DavidLapous/multipers).\n"; - - if (!stripComments && !rivetCompatible) file << "# Number of parameters\n"; - - if (rivetCompatible) { - assert(numberOfParameters == 2 && "Rivet only handles bifiltrations."); - file << "Filtration 1\n"; - file << "Filtration 2\n"; - } else { - file << std::to_string(numberOfParameters) << "\n"; - } - - if (!stripComments) file << "# Sizes of generating sets\n"; - - using Filtration_value = typename Slicer::Filtration_value; - - const auto& boundaries = slicer.get_structure(); - int maxDim = boundaries.max_dimension(); - int minDim = maxDim; - - std::vector > indicesByDim(maxDim + 1); - std::vector shiftedIndices(boundaries.size()); - for (std::size_t i = 0; i < boundaries.size(); ++i) { - auto dim = boundaries.dimension(i); - minDim = dim < minDim ? dim : minDim; - auto& atDim = indicesByDim[reverse ? dim : maxDim - dim]; - shiftedIndices[i] = atDim.size(); - atDim.push_back(i); - } - if (degree < 0) degree = minDim; - int minIndex = reverse ? degree - 1 : 0; - int maxIndex = reverse ? maxDim : maxDim - degree + 1; - if (maxIndex < -1) maxIndex = -1; - if (IgnoreLastGenerators) maxIndex--; - if (rivetCompatible) - minIndex = maxIndex -2; - - if constexpr (verbose) { - std::cout << "minDim = " << minDim << " maxDim = " << maxDim << " minIndex = " << minIndex - << " maxIndex = " << maxIndex << " degree = " << degree << std::endl; - } - - auto print_fil_values = [&](const Filtration_value& fil) { - if (fil.is_finite()) { - if constexpr (Filtration_value::is_multicritical()) { - for (const auto& ifil : fil) { - for (auto f : ifil) file << f << " "; - } - } else { - assert(fil.size() == static_cast(numberOfParameters)); - for (auto f : fil) file << f << " "; - } - } else { - // assert(fil.size() == 1); - for (int p = 0; p < numberOfParameters; ++p) file << fil[0] << " "; - } - }; - - if (minIndex < 0) file << 0 << " "; - for (int i = 0; i < minIndex; ++i) file << 0 << " "; - for (int i = std::max(minIndex, 0); i <= std::min(maxDim, maxIndex); ++i) { - file << indicesByDim[i].size() << " "; - } - if (!rivetCompatible) - for (int i = maxIndex + 1; i <= maxDim; ++i) file << 0 << " "; - if (maxIndex > maxDim) file << 0; - file << "\n"; - - file << std::setprecision(std::numeric_limits::digits); - - std::size_t startIndex = reverse ? minIndex + 1 : minIndex; - std::size_t endIndex = reverse ? maxIndex : maxIndex - 1; - const auto& filtValues = slicer.get_filtrations(); - int currDim; - if (reverse) - currDim = minIndex == -1 ? 0 : minIndex; - else - currDim = maxIndex == maxDim + 1 ? maxDim + 1 : maxDim; - - if (reverse) { - if (!stripComments) file << "# Block of dimension " << currDim++ << "\n"; - if (minIndex >= 0) { - for (auto index : indicesByDim[minIndex]) { - print_fil_values(filtValues[index]); - file << ";\n"; - } - } - } - for (std::size_t i = startIndex; i <= endIndex; ++i) { - if (!stripComments) { - file << "# Block of dimension " << currDim << "\n"; - if (reverse) - ++currDim; - else - --currDim; - } - for (auto index : indicesByDim[i]) { - print_fil_values(filtValues[index]); - file << "; "; - for (auto b : boundaries[index]) file << shiftedIndices[b] << " "; - file << "\n"; - } - } - if (!reverse) { - if (!stripComments) file << "# Block of dimension " << currDim << "\n"; - if (maxIndex <= maxDim) { - for (auto index : indicesByDim[maxIndex]) { - print_fil_values(filtValues[index]); - file << ";\n"; - } - } - } -}; - -#endif // MULTIPERS_SCC_IO_H diff --git a/multipers/gudhi/mma_interface_h0.h b/multipers/gudhi/tmp_h0_pers/mma_interface_h0.h similarity index 99% rename from multipers/gudhi/mma_interface_h0.h rename to multipers/gudhi/tmp_h0_pers/mma_interface_h0.h index a5f7bd5f..62f99f63 100644 --- a/multipers/gudhi/mma_interface_h0.h +++ b/multipers/gudhi/tmp_h0_pers/mma_interface_h0.h @@ -25,7 +25,7 @@ #include -#include +#include "naive_merge_tree.h" namespace Gudhi { namespace multiparameter { diff --git a/multipers/gudhi/naive_merge_tree.h b/multipers/gudhi/tmp_h0_pers/naive_merge_tree.h similarity index 100% rename from multipers/gudhi/naive_merge_tree.h rename to multipers/gudhi/tmp_h0_pers/naive_merge_tree.h diff --git a/multipers/gudhi/truc.h b/multipers/gudhi/truc.h deleted file mode 100644 index 79a9f550..00000000 --- a/multipers/gudhi/truc.h +++ /dev/null @@ -1,1403 +0,0 @@ -#pragma once -#include "gudhi/Matrix.h" -#include "gudhi/mma_interface_matrix.h" -#include "gudhi/Multi_persistence/Line.h" -#include "multiparameter_module_approximation/format_python-cpp.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include //std::invoke_result -#include "scc_io.h" - -namespace Gudhi { -namespace multiparameter { -namespace truc_interface { -using index_type = std::uint32_t; - -template -struct has_columns : std::false_type {}; - -template -struct has_columns> : std::true_type {}; - -class PresentationStructure { - public: - PresentationStructure() {} - - /* SimplicialStructure &operator=(const SimplicialStructure &) = default; */ - - PresentationStructure(const std::vector> &generators, - const std::vector &generator_dimensions) - : generators(generators), generator_dimensions(generator_dimensions), num_vertices_(0) { - for (const auto &stuff : generator_dimensions) { - if (stuff == 0) num_vertices_++; - } - max_dimension_ = generator_dimensions.size() > 0 - ? *std::max_element(generator_dimensions.begin(), generator_dimensions.end()) - : 0; - }; - - PresentationStructure(const PresentationStructure &other) - : generators(other.generators), - generator_dimensions(other.generator_dimensions), - num_vertices_(other.num_vertices_), - max_dimension_(other.max_dimension_) {} - - /* PresentationStructure &operator=(const PresentationStructure &other) { */ - /* generators = other.generators; */ - /* generator_dimensions = other.generator_dimensions; */ - /* num_vertices_ = other.num_vertices_; */ - /* max_dimension_ = other.max_dimension_; */ - /**/ - /* return *this; */ - /* } */ - - const std::vector &operator[](std::size_t i) const { - return generators[i]; - } // needs to be iterable (begin, end, size) - - std::vector &operator[](std::size_t i) { - return generators[i]; - } // needs to be iterable (begin, end, size) - - inline int dimension(std::size_t i) const { return generator_dimensions[i]; }; - - inline friend std::ostream &operator<<(std::ostream &stream, const PresentationStructure &structure) { - stream << "Boundary:\n"; - stream << "{\n"; - for (auto i : std::views::iota(0u, structure.size())) { - const auto &stuff = structure.generators[i]; - stream << i << ": {"; - for (auto truc : stuff) stream << truc << ", "; - - if (!stuff.empty()) stream << "\b" << "\b "; - - stream << "},\n"; - } - stream << "}\n"; - stream << "Degrees: (max " << structure.max_dimension() << ")\n"; - stream << "{"; - for (const auto &stuff : structure.generator_dimensions) stream << stuff << ", "; - if (structure.size() > 0) { - stream << "\b" << "\b"; - } - stream << "}\n"; - return stream; - } - - inline void to_stream(std::ostream &stream, const std::vector &order) { - for (const auto &i : order) { - const auto &stuff = this->operator[](i); - stream << i << " : ["; - for (const auto &truc : stuff) stream << truc << ", "; - stream << "]\n"; - } - /* return stream; */ - } - - inline std::size_t size() const { return generators.size(); }; - - unsigned int num_vertices() const { return num_vertices_; }; - - unsigned int max_dimension() const { return max_dimension_; } - - int prune_above_dimension(int dim) { - int idx = std::lower_bound(generator_dimensions.begin(), generator_dimensions.end(), dim + 1) - - generator_dimensions.begin(); - generators.resize(idx); - generator_dimensions.resize(idx); - max_dimension_ = generator_dimensions.size() ? generator_dimensions.back() : -1; - return idx; - } - - PresentationStructure permute(const std::vector &order) const { - if (order.size() > generators.size()) { - throw std::invalid_argument("Permutation order must have the same size as the number of generators."); - } - index_type flag = -1; - std::vector inverse_order(generators.size(), flag); - for (std::size_t i = 0; i < order.size(); i++) { - inverse_order[order[i]] = i; - } - std::vector> new_generators(order.size()); - std::vector new_generator_dimensions(order.size()); - - for (auto i : std::views::iota(0u, order.size())) { - new_generators[i].reserve(generators[order[i]].size()); - for (std::size_t j = 0; j < generators[order[i]].size(); j++) { - index_type stuff = inverse_order[generators[order[i]][j]]; - if (stuff != flag) new_generators[i].push_back(stuff); - } - std::sort(new_generators[i].begin(), new_generators[i].end()); - new_generator_dimensions[i] = generator_dimensions[order[i]]; - } - return PresentationStructure(new_generators, new_generator_dimensions); - } - - void update_matrix(std::vector> &new_gens) { std::swap(generators, new_gens); } - - private: - std::vector> generators; - std::vector generator_dimensions; - unsigned int num_vertices_; - int max_dimension_ = -1; -}; - -class SimplicialStructure { - public: - template - void from_simplextree(SimplexTree &st) { - auto [filtration, boundary] = Gudhi::multiparameter::mma::simplextree_to_ordered_bf(st); - this->boundaries = boundary; - this->num_vertices_ = st.num_vertices(); - this->max_dimension_ = st.dimension(); - } - - SimplicialStructure() {} - - /* SimplicialStructure &operator=(const SimplicialStructure &) = default; */ - - SimplicialStructure(const std::vector> &boundaries, - unsigned int num_vertices, - unsigned int max_dimension) - : boundaries(boundaries), num_vertices_(num_vertices), max_dimension_(max_dimension) { - - }; - - const std::vector &operator[](std::size_t i) const { - return boundaries[i]; - } // needs to be iterable (begin, end, size) - - std::vector &operator[](std::size_t i) { - return boundaries[i]; - } // needs to be iterable (begin, end, size) - - int dimension(std::size_t i) const { return boundaries[i].size() == 0 ? 0 : boundaries[i].size() - 1; }; - - inline friend std::ostream &operator<<(std::ostream &stream, const SimplicialStructure &structure) { - stream << "{"; - for (const auto &stuff : structure.boundaries) { - stream << "{"; - for (auto truc : stuff) stream << truc << ", "; - - if (!stuff.empty()) stream << "\b" << "\b "; - - stream << "},\n"; - } - stream << "}\n"; - return stream; - } - - inline void to_stream(std::ostream &stream, const std::vector &order) { - for (const auto &i : order) { - const auto &stuff = this->operator[](i); - stream << i << " : ["; - for (const auto &truc : stuff) stream << truc << ", "; - stream << "]\n"; - } - /* return stream; */ - } - - inline std::size_t size() const { return boundaries.size(); }; - - inline unsigned int num_vertices() const { return num_vertices_; } - - inline unsigned int max_dimension() const { return max_dimension_; } - - int prune_above_dimension([[maybe_unused]] int dim) { throw "Not implemented"; } - - private: - std::vector> boundaries; - unsigned int num_vertices_; - unsigned int max_dimension_; -}; - -template -class Truc { - public: - using Filtration_value = MultiFiltration; - using MultiFiltrations = std::vector; - using value_type = typename MultiFiltration::value_type; - using split_barcode = - std::vector>>; - using split_barcode_idx = std::vector>>; - template - using flat_barcode = std::vector>>; - - template - using flat_nodim_barcode = std::vector>; - - // CONSTRUCTORS. - // - Need everything of the same size, generator order is a PERMUTATION - // - Truc(const Structure &structure, const std::vector &generator_filtration_values) - : generator_filtration_values(generator_filtration_values), - generator_order(structure.size()), - structure(structure), - filtration_container(structure.size()) { - std::iota(generator_order.begin(), generator_order.end(), 0); // range - }; - - template - Truc(SimplexTree *simplextree) { - auto [filtration, boundary] = mma::simplextree_to_ordered_bf(*simplextree); - structure = SimplicialStructure(boundary, (*simplextree).num_vertices(), (*simplextree).dimension()); - generator_filtration_values.resize(filtration.size()); - for (auto i = 0u; i < filtration.size(); i++) - generator_filtration_values[i] = filtration[i]; // there is a copy here. TODO : deal with it. - generator_order = std::vector(structure.size()); - std::iota(generator_order.begin(), generator_order.end(), 0); // range - filtration_container.resize(structure.size()); - } - - Truc(const std::vector> &generator_maps, - const std::vector &generator_dimensions, - const std::vector &generator_filtrations) - : generator_filtration_values(generator_filtrations), - generator_order(generator_filtrations.size(), 0), - structure(PresentationStructure(generator_maps, generator_dimensions)), - filtration_container(generator_filtrations.size()) { - std::iota(generator_order.begin(), generator_order.end(), 0); // range - } - - Truc(const Truc &other) - : generator_filtration_values(other.generator_filtration_values), - generator_order(other.generator_order), - structure(other.structure), - filtration_container(other.filtration_container), - persistence(other.persistence) { - persistence._update_permutation_ptr(generator_order); - } - - Truc &operator=(Truc other) { - if (this != &other) { - generator_filtration_values = other.generator_filtration_values; - generator_order = other.generator_order; - structure = other.structure; - filtration_container = other.filtration_container; - persistence = other.persistence; - persistence._update_permutation_ptr(generator_order); - } - return *this; - } - - Truc() {}; - - inline bool dimension_order(const index_type &i, const index_type &j) const { - return structure.dimension(i) < structure.dimension(j); - }; - - inline bool colexical_order(const index_type &i, const index_type &j) const { - if (structure.dimension(i) > structure.dimension(j)) return false; - if (structure.dimension(i) < structure.dimension(j)) return true; - if constexpr (MultiFiltration::is_multicritical()) // TODO : this may not be the best - throw "Not implemented in the multicritical case"; - - for (int idx = generator_filtration_values[i].num_parameters() - 1; idx >= 0; --idx) { - if (generator_filtration_values[i][idx] < generator_filtration_values[j][idx]) - return true; - else if (generator_filtration_values[i][idx] > generator_filtration_values[j][idx]) - return false; - } - return false; - }; - - // TODO : inside of MultiFiltration - inline static bool lexical_order(const MultiFiltration &a, const MultiFiltration &b) { - if constexpr (MultiFiltration::is_multicritical()) // TODO : this may not be the best - throw "Not implemented in the multicritical case"; - if (a.is_plus_inf() || a.is_nan() || b.is_minus_inf()) return false; - if (b.is_plus_inf() || b.is_nan() || a.is_minus_inf()) return true; - for (auto idx = 0u; idx < a.num_parameters(); ++idx) { - if (a[idx] < b[idx]) - return true; - else if (a[idx] > b[idx]) - return false; - } - return false; - }; - - inline bool lexical_order(const index_type &i, const index_type &j) const { - if (structure.dimension(i) > structure.dimension(j)) return false; - if (structure.dimension(i) < structure.dimension(j)) return true; - if constexpr (MultiFiltration::is_multicritical()) // TODO : this may not be the best - throw "Not implemented in the multicritical case"; - - for (int idx = 0; idx < generator_filtration_values[i].num_parameters(); ++idx) { - if (generator_filtration_values[i][idx] < generator_filtration_values[j][idx]) - return true; - else if (generator_filtration_values[i][idx] > generator_filtration_values[j][idx]) - return false; - } - return false; - }; - - inline Truc permute(const std::vector &permutation) const { - auto num_new_gen = permutation.size(); - if (permutation.size() > this->num_generators()) { - throw std::invalid_argument("Invalid permutation size. Got " + std::to_string(num_new_gen) + " expected " + - std::to_string(this->num_generators()) + "."); - } - std::vector new_filtration(num_new_gen); - for (auto i : std::views::iota(0u, num_new_gen)) { // assumes permutation has correct indices. - new_filtration[i] = generator_filtration_values[permutation[i]]; - } - return Truc(structure.permute(permutation), new_filtration); - } - - template - inline std::pair> rearange_sort(const Fun &&fun) const { - std::vector permutation(generator_order.size()); - std::iota(permutation.begin(), permutation.end(), 0); - tbb::parallel_sort(permutation.begin(), permutation.end(), [&](std::size_t i, std::size_t j) { return fun(i, j); }); - return {permute(permutation), permutation}; - } - - std::pair> colexical_rearange() const { - return rearange_sort([this](std::size_t i, std::size_t j) { return this->colexical_order(i, j); }); - } - - template - std::conditional_t>, MultiFiltrations>, Truc> - projective_cover_kernel(int dim) { - if constexpr (MultiFiltration::is_multicritical() || !std::is_same_v || - !has_columns::value) // TODO : this may not be the best - { - throw std::invalid_argument("Not implemented for this Truc"); - } else { - // TODO : this only works for 2 parameter modules. Optimize w.r.t. this. - const bool verbose = false; - // filtration values are assumed to be dim + colexicographically sorted - // vector seem to be good here - using SmallMatrix = Gudhi::persistence_matrix::Matrix< - Gudhi::multiparameter::truc_interface::fix_presentation_options>; - - int nd = 0; - int ndpp = 0; - for (auto i : std::views::iota(0u, structure.size())) { - if (structure.dimension(i) == dim) { - nd++; - } else if (structure.dimension(i) == dim + 1) { - ndpp++; - } else { - throw std::invalid_argument("This truc contains bad dimensions. Got " + - std::to_string(structure.dimension(i)) + " expected " + std::to_string(dim) + - " or " + std::to_string(dim + 1) + " in position " + std::to_string(i) + " ."); - } - } - if (ndpp == 0) - throw std::invalid_argument("Given dimension+1 has no simplices. Got " + std::to_string(nd) + " " + - std::to_string(ndpp) + "."); - // lexico iterator - auto lex_cmp = [&](const MultiFiltration &a, const MultiFiltration &b) { return lexical_order(a, b); }; - - struct SmallQueue { - SmallQueue() {}; - - struct MFWrapper { - MFWrapper(const MultiFiltration &g) : g(g) {}; - - MFWrapper(const MultiFiltration &g, int col) : g(g) { some_cols.insert(col); } - - MFWrapper(const MultiFiltration &g, std::initializer_list cols) - : g(g), some_cols(cols.begin(), cols.end()) {} - - inline void insert(int col) const { some_cols.insert(col); } - - inline bool operator<(const MFWrapper &other) const { return lexical_order(g, other.g); } - - public: - MultiFiltration g; - mutable std::set some_cols; - }; - - inline void insert(const MultiFiltration &g, int col) { - auto it = queue.find(g); - if (it != queue.end()) { - it->insert(col); - } else { - queue.emplace(g, col); - } - }; - - inline void insert(const MultiFiltration &g, const std::initializer_list &cols) { - auto it = queue.find(g); - if (it != queue.end()) { - for (int c : cols) it->insert(c); - } else { - queue.emplace(g, cols); - } - }; - - inline bool empty() const { return queue.empty(); } - - inline MultiFiltration pop() { - if (queue.empty()) [[unlikely]] - throw std::runtime_error("Queue is empty"); - - auto out = std::move(*queue.begin()); - queue.erase(queue.begin()); - std::swap(last_cols, out.some_cols); - return out.g; - } - - const auto &get_current_cols() const { return last_cols; } - - private: - std::set queue; - std::set last_cols; - }; - - SmallQueue lexico_it; - SmallMatrix M(nd + ndpp); - for (int i = 0; i < nd + ndpp; i++) { - const auto &b = structure[i]; - M.insert_boundary(b); - } - SmallMatrix N(nd + ndpp); // slave - for (auto i : std::views::iota(0u, static_cast(nd + ndpp))) { - N.insert_boundary({i}); - }; - - auto get_fil = [&](int i) -> MultiFiltration & { return generator_filtration_values[i]; }; - auto get_pivot = [&](int j) -> int { - const auto &col = M.get_column(j); - return col.size() > 0 ? (*col.rbegin()).get_row_index() : -1; - }; - - if constexpr (verbose) { - std::cout << "Initial matrix (" << nd << " + " << ndpp << "):" << std::endl; - for (int i = 0; i < nd + ndpp; i++) { - std::cout << "Column " << i << " : {"; - for (const auto &j : M.get_column(i)) std::cout << j << " "; - std::cout << "} | " << get_fil(i) << std::endl; - } - } - - // TODO : pivot caches are small : maybe use a flat container instead ? - std::vector> pivot_cache(nd + ndpp); // this[pivot] = cols of given pivot (<=nd) - std::vector reduced_columns(nd + ndpp); // small cache - MultiFiltration grid_value; - - std::vector> out_structure; - out_structure.reserve(2 * ndpp); - std::vector out_filtration; - out_filtration.reserve(2 * ndpp); - std::vector out_dimension; - out_dimension.reserve(2 * ndpp); - if constexpr (!generator_only) { - for (auto i : std::views::iota(nd, nd + ndpp)) { - out_structure.push_back({}); - out_filtration.push_back(this->get_filtration_values()[i]); - out_dimension.push_back(this->structure.dimension(i)); - } - } - // pivot cache - if constexpr (verbose) { - std::cout << "Initial pivot cache:\n"; - } - for (int j : std::views::iota(nd, nd + ndpp)) { - int col_pivot = get_pivot(j); - if (col_pivot < 0) { - reduced_columns[j] = true; - continue; - }; - auto ¤t_pivot_cache = pivot_cache[col_pivot]; - current_pivot_cache.emplace_hint(current_pivot_cache.cend(), j); // j is increasing - } - if constexpr (verbose) { - int i = 0; - for (const auto &cols : pivot_cache) { - std::cout << " - " << i++ << " : "; - for (const auto &col : cols) { - std::cout << col << " "; - } - std::cout << std::endl; - } - } - - // if constexpr (!use_grid) { - if constexpr (verbose) std::cout << "Initial grid queue:\n"; - for (int j : std::views::iota(nd, nd + ndpp)) { - int col_pivot = get_pivot(j); - if (col_pivot < 0) continue; - lexico_it.insert(get_fil(j), j); - auto it = pivot_cache[col_pivot].find(j); - if (it == pivot_cache[col_pivot].end()) [[unlikely]] - throw std::runtime_error("Column " + std::to_string(j) + " not in pivot cache"); - it++; - // for (int k : pivot_cache[col_pivot]) { - for (auto _k = it; _k != pivot_cache[col_pivot].end(); ++_k) { - int k = *_k; - if (k <= j) [[unlikely]] - throw std::runtime_error("Column " + std::to_string(k) + " is not a future column"); - auto prev = get_fil(k); - prev.push_to_least_common_upper_bound(get_fil(j)); - if constexpr (verbose) std::cout << " - (" << j << ", " << k << ") are interacting at " << prev << "\n"; - lexico_it.insert(std::move(prev), k); - } - } - // TODO : check poset cache ? - if constexpr (verbose) std::cout << std::flush; - // } - auto reduce_column = [&](int j) -> bool { - int pivot = get_pivot(j); - if constexpr (verbose) std::cout << "Reducing column " << j << " with pivot " << pivot << "\n"; - if (pivot < 0) { - if (!reduced_columns[j]) { - std::vector _b(N.get_column(j).begin(), N.get_column(j).end()); - for (auto &stuff : _b) stuff -= nd; - out_structure.push_back(std::move(_b)); - out_filtration.emplace_back(grid_value.begin(), grid_value.end()); - if constexpr (!generator_only) out_dimension.emplace_back(this->structure.dimension(j) + 1); - reduced_columns[j] = true; - } - return false; - } - if constexpr (verbose) std::cout << "Previous registered pivot : " << *pivot_cache[pivot].begin() << std::endl; - // WARN : we lazy update variables linked with col j... - if (pivot_cache[pivot].size() == 0) { - return false; - } - for (int k : pivot_cache[pivot]) { - if (k >= j) { // cannot reduce more here. this is a (local) pivot. - return false; - } - if (get_fil(k) <= grid_value) { - M.add_to(k, j); - N.add_to(k, j); - // std::cout << "Adding " << k << " to " << j << " at grid time " << grid_value << std::endl; - pivot_cache[pivot].erase(j); - // WARN : we update the pivot cache after the update loop - if (get_pivot(j) >= pivot) { - throw std::runtime_error("Addition failed ? current " + std::to_string(get_pivot(j)) + " previous " + - std::to_string(pivot)); - } - return true; // pivot has changed - } - } - return false; // for loop exhausted (j may not be there because of lazy) - }; - auto chores_after_new_pivot = [&](int j) { - int col_pivot = get_pivot(j); - if (col_pivot < 0) { - if (!reduced_columns[j]) throw std::runtime_error("Empty column should have been detected before"); - return; - }; - auto [it, was_there] = pivot_cache[col_pivot].insert(j); - it++; - // if constexpr (!use_grid) { - for (auto _k = it; _k != pivot_cache[col_pivot].end(); ++_k) { - int k = *_k; - if (k <= j) [[unlikely]] - throw std::runtime_error("(chores) col " + std::to_string(k) + " is not a future column"); - if (get_fil(k) >= get_fil(j)) continue; - auto prev = get_fil(k); - prev.push_to_least_common_upper_bound(get_fil(j)); - if (lex_cmp(grid_value, prev)) { - if constexpr (verbose) - std::cout << "(chores) Updating grid queue, (" << j << ", " << k << ") are interacting at " << prev - << std::endl; - lexico_it.insert(prev, k); - } - } - // } - }; - if constexpr (verbose) { - std::cout << "Initially reduced columns: ["; - for (int i = 0; i < nd + ndpp; i++) { - std::cout << reduced_columns[i] << ", "; - } - std::cout << "]" << std::endl; - } - while (!lexico_it.empty()) { - // if constexpr (use_grid) { - // grid_value = lexico_it.next(); - // } else { - grid_value = std::move(lexico_it.pop()); - // } - if constexpr (verbose) { - std::cout << "Grid value: " << grid_value << std::endl; - std::cout << " Reduced cols: "; - for (int i = 0; i < nd + ndpp; i++) { - std::cout << reduced_columns[i] << ", "; - } - std::cout << "]" << std::endl; - } - - for (int i : lexico_it.get_current_cols()) { - if constexpr (false) { - if ((reduced_columns[i] || !(get_fil(i) <= grid_value))) continue; - if ((get_fil(i) > grid_value)) break; - } - while (reduce_column(i)); - chores_after_new_pivot(i); - } - } - // std::cout<< grid_.str() << std::endl; - if constexpr (generator_only) - return {out_structure, out_dimension}; - else { - return Truc(out_structure, out_dimension, out_filtration); - } - } - } - - template - std::vector>> get_current_boundary_matrix() { - std::vector permutation(generator_order.size()); - std::iota(permutation.begin(), permutation.end(), 0); - if constexpr (ignore_inf) { - permutation.erase(std::remove_if(permutation.begin(), - permutation.end(), - [&](std::size_t val) { - return filtration_container[val] == MultiFiltration::Generator::T_inf; - }), - permutation.end()); - tbb::parallel_sort(permutation.begin(), permutation.end()); - } - tbb::parallel_sort(permutation.begin(), permutation.end(), [&](std::size_t i, std::size_t j) { - if (structure.dimension(i) > structure.dimension(j)) return false; - if (structure.dimension(i) < structure.dimension(j)) return true; - return filtration_container[i] < filtration_container[j]; - }); - - std::vector>> matrix(permutation.size()); - - std::vector permutationInv(generator_order.size()); - std::size_t newPos = 0; - for (std::size_t oldPos : permutation) { - permutationInv[oldPos] = newPos; - auto &boundary = matrix[newPos].second; - boundary.resize(structure[oldPos].size()); - for (std::size_t j = 0; j < structure[oldPos].size(); ++j) { - boundary[j] = permutationInv[structure[oldPos][j]]; - } - std::sort(boundary.begin(), boundary.end()); - matrix[newPos].first = structure.dimension(oldPos); - ++newPos; - } - - return matrix; - } - - inline std::size_t num_generators() const { return structure.size(); } - - inline std::size_t num_parameters() const { - return num_generators() == 0 ? 0 : this->generator_filtration_values[0].num_parameters(); - } - - inline const Structure &get_structure() const { return structure; } - - template - inline void push_to_out(const SubFiltration &f, - std::vector &filtration_container, - const std::vector &generator_order) const { - /* std::vector out(this->num_generators()); */ - - // filtration_container.resize( - // this->num_generators()); // for some reasons it is necessary FIXME - for (std::size_t i = 0u; i < this->num_generators(); i++) { - if constexpr (original_order) { - filtration_container[i] = f.compute_forward_intersection(generator_filtration_values[i]); - } else { - filtration_container[i] = f.compute_forward_intersection(generator_filtration_values[generator_order[i]]); - } - } - } - - template - inline void push_to(const SubFiltration &f) { - this->push_to_out(f, this->filtration_container, this->generator_order); - } - - template - inline void set_one_filtration(const array1d &truc) { - if (truc.size() != this->num_generators()) - throw std::invalid_argument("(setting one filtration) Bad size. Got " + std::to_string(truc.size()) + - " expected " + std::to_string(this->num_generators())); - this->filtration_container = truc; - } - - inline const std::vector &get_one_filtration() const { - return this->filtration_container; - } - - inline PersBackend compute_persistence_out( - const std::vector &one_filtration, - std::vector &out_gen_order, - const bool ignore_inf) const { // needed ftm as PersBackend only points there - constexpr const bool verbose = false; - if (one_filtration.size() != this->num_generators()) { - throw std::runtime_error("The one parameter filtration doesn't have a proper size."); - } - out_gen_order.resize(this->num_generators()); - std::iota(out_gen_order.begin(), - out_gen_order.end(), - 0); // we have to reset here, even though we're already doing this - std::sort(out_gen_order.begin(), out_gen_order.end(), [&](index_type i, index_type j) { - if (structure.dimension(i) > structure.dimension(j)) return false; - if (structure.dimension(i) < structure.dimension(j)) return true; - return one_filtration[i] < one_filtration[j]; - }); - if (!PersBackend::is_vine && ignore_inf) { - if constexpr (verbose) { - std::cout << "Removing infinite simplices" << std::endl; - } - for (auto &i : out_gen_order) - if (one_filtration[i] == MultiFiltration::Generator::T_inf) { - // TODO : later - // int d = structure.dimension(i); - // d = d == 0 ? 1 : 0; - // if (degrees.size()>d || degrees[d] || degrees[d-1]) - // continue; - i = std::numeric_limits::value_type>::max(); - } - } - if constexpr (false) { - std::cout << structure << std::endl; - std::cout << "["; - for (auto i : out_gen_order) { - std::cout << i << ", "; - } - std::cout << "]" << std::endl; - std::cout << "["; - for (auto i : one_filtration) { - std::cout << i << ","; - } - std::cout << "]" << std::endl; - } - return PersBackend(structure, out_gen_order); - } - - inline bool has_persistence() const { return this->persistence.size(); }; - - inline void compute_persistence(const bool ignore_inf = true) { - this->persistence = this->compute_persistence_out( - // this->filtration_container, this->generator_order, degrees); // TODO - // : later - this->filtration_container, - this->generator_order, - ignore_inf); - }; - - // TODO : static ? - inline void vineyard_update(PersBackend &persistence, - const std::vector &one_filtration, - std::vector &generator_order) const { - constexpr const bool verbose = false; - /* static_assert(PersBackend::has_vine_update); */ - // the first false is to get the generator order - // insertion sort - auto n = this->num_generators(); - if constexpr (verbose) { - std::cout << "Vine updates : "; - } - for (std::size_t i = 0; i < n; i++) { - auto j = i; - while (j > 0 && persistence.get_dimension(j) == persistence.get_dimension(j - 1) && - one_filtration[generator_order[j]] < one_filtration[generator_order[j - 1]]) { - if constexpr (verbose) { - std::cout << j - 1 << ", "; - } - persistence.vine_swap(j - 1); - std::swap(generator_order[j - 1], generator_order[j]); - j--; - } - } - if constexpr (verbose) { - std::cout << std::endl; - } - } - - inline void vineyard_update() { - vineyard_update(this->persistence, this->filtration_container, this->generator_order); - } - - inline split_barcode_idx get_barcode_idx( - PersBackend &persistence) const { - auto barcode_indices = persistence.get_barcode(); - split_barcode_idx out(this->structure.max_dimension() + 1); // TODO : This doesn't allow for negative dimensions - for (const auto &bar : barcode_indices) { - int death = bar.death == static_cast(-1) ? -1 : bar.death; - out[bar.dim].push_back({bar.birth, death}); - } - return out; - } - - // puts the degree-ordered bc starting out_ptr, and returns the "next" pointer. - // corresond to an array of shape (num_bar, 2); - template - inline std::conditional_t, int*>, int*> get_barcode_idx( - PersBackend &persistence, - int *start_ptr) const { - const auto &bc = persistence.barcode(); - if (bc.size() == 0) return start_ptr; - std::vector shape(this->structure.max_dimension()); - for (const auto &b : bc) shape[b.dim]++; - // dim in barcode may be unsorted... - std::vector ptr_shifts(shape.size()); - int shape_cumsum = 0; - for (auto i : std::views::iota(0u, bc.size())) { - if (i != 0u) shape_cumsum += shape[i - 1]; - // 2 for (birth, death) - ptr_shifts[i] = 2 * shape_cumsum + start_ptr; - } - for (const auto &b : bc) { - int *current_loc = ptr_shifts[b.dim]; - *(current_loc++) = b.birth; - *(current_loc++) = b.death == static_cast(-1) ? -1 : b.death; - } - - if constexpr (return_shape) - return {shape, ptr_shifts.back()}; - else - return ptr_shifts.back(); - } - - - - inline split_barcode get_barcode( - PersBackend &persistence, - const std::vector &filtration_container) const { - auto barcode_indices = persistence.get_barcode(); - split_barcode out(this->structure.max_dimension() + 1); // TODO : This doesn't allow for negative dimensions - constexpr const bool verbose = false; - constexpr const bool debug = false; - const auto inf = MultiFiltration::Generator::T_inf; - for (const auto &bar : barcode_indices) { - if constexpr (verbose) std::cout << "BAR : " << bar.birth << " " << bar.death << "\n"; - if constexpr (debug) { - if (bar.birth >= filtration_container.size() || bar.birth < 0) { - std::cout << "Trying to add an incompatible birth... "; - std::cout << bar.birth << std::endl; - std::cout << "Death is " << bar.death << std::endl; - std::cout << "Max size is " << filtration_container.size() << std::endl; - continue; - } - if (bar.dim > static_cast(this->structure.max_dimension())) { - std::cout << "Incompatible dimension detected... " << bar.dim << std::endl; - std::cout << "While max dim is " << this->structure.max_dimension() << std::endl; - continue; - } - } - - auto birth_filtration = filtration_container[bar.birth]; - auto death_filtration = inf; - if (bar.death != static_cast(-1)) - death_filtration = filtration_container[bar.death]; - - if constexpr (verbose) { - std::cout << "BAR: " << bar.birth << "(" << birth_filtration << ")" - << " --" << bar.death << "(" << death_filtration << ")" - << " dim " << bar.dim << std::endl; - } - if (birth_filtration <= death_filtration) - out[bar.dim].push_back({birth_filtration, death_filtration}); - else { - out[bar.dim].push_back({inf, inf}); - } - } - return out; - } - - inline split_barcode get_barcode() { return get_barcode(this->persistence, this->filtration_container); } - - inline split_barcode_idx get_barcode_idx() { return get_barcode_idx(this->persistence); } - - template - static inline flat_nodim_barcode get_flat_nodim_barcode( - PersBackend &persistence, - std::vector &filtration_container) { - constexpr const bool verbose = false; - const auto &barcode_indices = persistence.get_barcode(); - auto num_bars = barcode_indices.size(); - flat_nodim_barcode out(num_bars); - if (num_bars <= 0) return out; - auto idx = 0u; - const value_type inf = MultiFiltration::Generator::T_inf; - for (const auto &bar : barcode_indices) { - value_type birth_filtration = inf; - value_type death_filtration = -birth_filtration; - if (bar.death == static_cast(-1)) - death_filtration = inf; - else - death_filtration = static_cast(filtration_container[bar.death]); - birth_filtration = static_cast(filtration_container[bar.birth]); - if constexpr (verbose) { - std::cout << "PAIRING : " << bar.birth << " / " << bar.death << " dim " << bar.dim << std::endl; - } - if constexpr (verbose) { - std::cout << "PAIRING filtration : " << birth_filtration << " " << death_filtration << " dim " << bar.dim - << std::endl; - } - - if (birth_filtration <= death_filtration) - out[idx] = {birth_filtration, death_filtration}; - else { - out[idx] = {inf, inf}; - } - idx++; - } - return out; - } - - template - static inline flat_barcode get_flat_barcode( - PersBackend &persistence, - std::vector &filtration_container) { - constexpr const bool verbose = false; - const auto &barcode_indices = persistence.get_barcode(); - auto num_bars = barcode_indices.size(); - flat_barcode out(num_bars); - if (num_bars <= 0) return out; - auto idx = 0u; - const value_type inf = MultiFiltration::Generator::T_inf; - for (const auto &bar : barcode_indices) { - value_type birth_filtration = inf; - value_type death_filtration = -birth_filtration; - if (bar.death == static_cast(-1)) - death_filtration = inf; - else - death_filtration = static_cast(filtration_container[bar.death]); - birth_filtration = static_cast(filtration_container[bar.birth]); - if constexpr (verbose) { - std::cout << "PAIRING : " << bar.birth << " / " << bar.death << " dim " << bar.dim << std::endl; - } - if constexpr (verbose) { - std::cout << "PAIRING filtration : " << birth_filtration << " " << death_filtration << " dim " << bar.dim - << std::endl; - } - - if (birth_filtration <= death_filtration) - out[idx] = {bar.dim, {birth_filtration, death_filtration}}; - else { - out[idx] = {bar.dim, {inf, inf}}; - } - idx++; - } - return out; - } - - template - inline flat_barcode get_flat_barcode() { - return get_flat_barcode(this->persistence, this->filtration_container); - } - - template - inline flat_nodim_barcode get_flat_nodim_barcode() { - return get_flat_nodim_barcode(this->persistence, this->filtration_container); - } - - inline friend std::ostream &operator<<(std::ostream &stream, Truc &truc) { - stream << "-------------------- Truc \n"; - stream << "--- Structure \n"; - stream << truc.structure; - /* stream << "-- Dimensions (max " << truc.structure.max_dimension() << - * ")\n"; */ - /* stream << "{"; */ - /* for (auto i = 0u; i < truc.num_generators(); i++) */ - /* stream << truc.structure.dimension(i) << ", "; */ - /* stream << "\b" */ - /* << "\b"; */ - /* stream << "}" << std::endl; */ - stream << "--- Order \n"; - stream << "{"; - for (const auto &idx : truc.generator_order) stream << idx << ", "; - stream << "}" << std::endl; - - stream << "--- Current slice filtration\n"; - stream << "{"; - for (const auto &stuff : truc.filtration_container) stream << stuff << ", "; - stream << "\b" << "\b"; - stream << "}" << std::endl; - - stream << "--- Filtrations \n"; - for (const auto &i : truc.generator_order) { - stream << i << " : "; - const auto &stuff = truc.generator_filtration_values[i]; - stream << stuff << "\n"; - } - stream << "--- PersBackend \n"; - stream << truc.persistence; - - return stream; - } - - inline std::string to_str() { - std::stringstream stream; - stream << *this; - return stream.str(); - } - - inline std::pair get_bounding_box() const { - using OC = typename MultiFiltration::Generator; - // assert(!generator_filtration_values.empty()); - OC a = OC::inf(); - OC b = -1 * a; - for (const auto &filtration_value : generator_filtration_values) { - if constexpr (MultiFiltration::is_multi_critical) { - a.pull_to_greatest_common_lower_bound(factorize_below(filtration_value)); - b.push_to_least_common_upper_bound(factorize_above(filtration_value)); - } else { - a.pull_to_greatest_common_lower_bound(filtration_value); - b.push_to_least_common_upper_bound(filtration_value); - } - } - return {a, b}; - } - - inline std::vector get_filtration_values() const { - if constexpr (MultiFiltration::is_multi_critical) { - std::vector out; - out.reserve(generator_filtration_values.size()); // at least this, will dooble later - for (std::size_t i = 0; i < generator_filtration_values.size(); ++i) { - for (const auto &f : generator_filtration_values[i]) { - out.push_back(f); - } - } - return out; - } else { - return generator_filtration_values; // copy not necessary for Generator - } // (could return const&) - } - - inline std::vector &get_filtrations() { return generator_filtration_values; } - - inline const std::vector &get_filtrations() const { return generator_filtration_values; } - - inline const std::vector get_dimensions() const { - std::size_t n = this->num_generators(); - std::vector out(n); - for (std::size_t i = 0; i < n; ++i) { - out[i] = structure.dimension(i); - } - return out; - } - - inline int get_dimension(int i) const { return structure.dimension(i); } - - inline void prune_above_dimension(int max_dim) { - int idx = structure.prune_above_dimension(max_dim); - generator_filtration_values.resize(idx); - generator_order.resize(idx); - filtration_container.resize(idx); - } - - inline const std::vector> get_boundaries() { - std::size_t n = this->num_generators(); - std::vector> out(n); - for (auto i = 0u; i < n; ++i) { - out[i] = this->structure[i]; - } - return out; - } - - auto coarsen_on_grid(const std::vector>& grid) { - using return_type = decltype(std::declval().template as_type()); - std::vector coords(this->num_generators()); - // for (std::size_t gen = 0u; gen < coords.size(); ++gen) { // TODO : parallelize - // coords[gen] = compute_coordinates_in_grid(generator_filtration_values[gen], grid); - // } - tbb::parallel_for(static_cast(0u), coords.size(), [&](std::size_t gen){ - coords[gen] = compute_coordinates_in_grid(generator_filtration_values[gen], grid); - }); - return Truc(structure, coords); - } - - inline void coarsen_on_grid_inplace(const std::vector> &grid, - bool coordinate = true) { - for (auto gen = 0u; gen < this->num_generators(); ++gen) { - generator_filtration_values[gen].project_onto_grid(grid, coordinate); - } - } - - // dim, num_cycle_of_dim, num_faces_in_cycle, vertices_in_face - inline std::vector>>> get_representative_cycles( - bool update = true, - bool detailed = false) { - // iterable iterable simplex key - auto cycles_key = persistence.get_representative_cycles(update, detailed); - auto num_cycles = cycles_key.size(); - std::vector>>> out(structure.max_dimension() + 1); - for (auto &cycles_of_dim : out) cycles_of_dim.reserve(num_cycles); - for (const auto &cycle : cycles_key) { - int cycle_dim = 0; // for more generality, should be minimal dimension instead - if (!cycle[0].empty()) { // if empty, cycle has no border -> assumes dimension 0 even if it could be min dim - cycle_dim = structure.dimension(cycle[0][0]) + 1; // all faces have the same dim - } - out[cycle_dim].push_back(cycle); - } - return out; - } - - const std::vector &get_current_order() const { return generator_order; } - - const PersBackend &get_persistence() const { return persistence; } - - PersBackend &get_persistence() { return persistence; } - - // TrucThread get_safe_thread() { return TrucThread(*this); } - - class TrucThread { - public: - using Filtration_value = MultiFiltration; - using value_type = typename MultiFiltration::value_type; - using ThreadSafe = TrucThread; - - inline TrucThread(const Truc &truc) - : truc_ptr(&truc), - generator_order(truc.get_current_order()), - filtration_container(truc.get_one_filtration()), - persistence(truc.get_persistence()) { - persistence._update_permutation_ptr(generator_order); - }; - - inline TrucThread(const TrucThread &truc) - : truc_ptr(truc.truc_ptr), - generator_order(truc.get_current_order()), - filtration_container(truc.get_one_filtration()), - persistence(truc.get_persistence()) { - persistence._update_permutation_ptr(generator_order); - }; - - inline TrucThread weak_copy() const { return TrucThread(*truc_ptr); } - - inline bool has_persistence() const { return this->persistence.size(); }; - - inline const PersBackend &get_persistence() const { return persistence; } - - inline PersBackend &get_persistence() { return persistence; } - - inline std::pair get_bounding_box() const { return truc_ptr->get_bounding_box(); } - - inline const std::vector &get_current_order() const { return generator_order; } - - inline const std::vector &get_filtrations() const { return truc_ptr->get_filtrations(); } - - inline const std::vector &get_dimensions() const { return truc_ptr->get_dimensions(); } - - inline const std::vector> &get_boundaries() const { return truc_ptr->get_boundaries(); } - - inline void coarsen_on_grid_inplace(const std::vector> &grid, - bool coordinate = true) { - truc_ptr->coarsen_on_grid_inplace(grid, coordinate); - } - - template - inline void push_to(const Subfiltration &f) { - truc_ptr->push_to_out(f, this->filtration_container, this->generator_order); - } - - inline std::vector get_representative_cycles(bool update = true) { - return truc_ptr->get_representative_cycles(update); - } - - inline void compute_persistence(const bool ignore_inf = true) { - this->persistence = - this->truc_ptr->compute_persistence_out(this->filtration_container, this->generator_order, ignore_inf); - }; - - inline void vineyard_update() { - truc_ptr->vineyard_update(this->persistence, this->filtration_container, this->generator_order); - } - - template - inline flat_barcode get_flat_barcode() { - return truc_ptr->get_flat_barcode(this->persistence, this->filtration_container); - } - - template - inline flat_nodim_barcode get_flat_nodim_barcode() { - return truc_ptr->get_flat_nodim_barcode(this->persistence, this->filtration_container); - } - - inline split_barcode get_barcode() { return truc_ptr->get_barcode(this->persistence, this->filtration_container); } - - inline split_barcode_idx get_barcode_idx() { - return truc_ptr->get_barcode_idx(this->persistence); - } - - inline std::size_t num_generators() const { return this->truc_ptr->structure.size(); } - - inline std::size_t num_parameters() const { - return num_generators() == 0 ? 0 : this->get_filtrations()[0].num_parameters(); - } - - inline const std::vector &get_one_filtration() const { - return this->filtration_container; - } - - inline std::vector &get_one_filtration() { - return this->filtration_container; - } - - template - inline void set_one_filtration(const array1d &truc) { - if (truc.size() != this->num_generators()) - throw std::invalid_argument("(setting one filtration) Bad size. Got " + std::to_string(truc.size()) + - " expected " + std::to_string(this->num_generators())); - this->filtration_container = truc; - } - - private: - const Truc *truc_ptr; - std::vector generator_order; // size fixed at construction time, - std::vector filtration_container; // filtration of the current slice - PersBackend persistence; // generated by the structure, and generator_order. - - }; // class TrucThread - - /* - * returns barcodes of the f(multipers) - * - */ - template - inline std::conditional_t, std::vector> - barcodes(Fun &&f, const std::vector &args, const bool ignore_inf = true) { - if (args.size() == 0) { - return {}; - } - std::conditional_t, std::vector> out(args.size()); - - if constexpr (PersBackend::is_vine) { - if constexpr (custom) - this->set_one_filtration(f(args[0])); - else - this->push_to(f(args[0])); - this->compute_persistence(); - if constexpr (idx) - out[0] = this->get_barcode_idx(); - else - out[0] = this->get_barcode(); - for (auto i = 1u; i < args.size(); ++i) { - if constexpr (custom) - this->set_one_filtration(f(args[i])); - else - this->push_to(f(args[i])); - this->vineyard_update(); - if constexpr (idx) - out[i] = this->get_barcode_idx(); - else - out[i] = this->get_barcode(); - } - - } else { - ThreadSafe local_template = this->weak_copy(); - tbb::enumerable_thread_specific thread_locals(local_template); - tbb::parallel_for(static_cast(0), args.size(), [&](const std::size_t &i) { - ThreadSafe &s = thread_locals.local(); - if constexpr (custom) - s.set_one_filtration(f(args[i])); - else - s.push_to(f(args[i])); - s.compute_persistence(ignore_inf); - if constexpr (idx) { - out[i] = s.get_barcode_idx(); - } else - out[i] = s.get_barcode(); - }); - } - return out; - } - - // FOR Python interface, but I'm not fan. Todo: do the lambda function in - // cython? - inline std::vector persistence_on_lines(const std::vector> &basepoints, - bool ignore_inf) { - return barcodes( - [](const std::vector &basepoint) { return Gudhi::multi_persistence::Line(basepoint); }, - basepoints, - ignore_inf); - } - - inline std::vector custom_persistences(const value_type *filtrations, int size, bool ignore_inf) { - std::vector args(size); - for (auto i = 0; i < size; ++i) args[i] = filtrations + this->num_generators() * i; - - auto fun = [&](const value_type *one_filtration_ptr) { - std::vector fil(this->num_generators()); - for (auto i : std::views::iota(0u, this->num_generators())) { - fil[i] = *(one_filtration_ptr + i); - } - return std::move(fil); - }; - return barcodes(std::move(fun), args, ignore_inf); - } - - inline std::vector persistence_on_lines( - const std::vector, std::vector>> &bp_dirs, - bool ignore_inf) { - return barcodes( - [](const std::pair, std::vector> &bpdir) { - return Gudhi::multi_persistence::Line(bpdir.first, bpdir.second); - }, - bp_dirs, - ignore_inf); - } - - void build_from_scc_file(const std::string &inFilePath, - bool isRivetCompatible = false, - bool isReversed = false, - int shiftDimensions = 0) { - *this = read_scc_file(inFilePath, isRivetCompatible, isReversed, shiftDimensions); - } - - void write_to_scc_file(const std::string &outFilePath, - int numberOfParameters = -1, - int degree = -1, - bool rivetCompatible = false, - bool IgnoreLastGenerators = false, - bool stripComments = false, - bool reverse = false) { - write_scc_file( - outFilePath, *this, numberOfParameters, degree, rivetCompatible, IgnoreLastGenerators, stripComments, reverse); - } - - public: - using ThreadSafe = TrucThread; // for outside - - TrucThread weak_copy() const { return TrucThread(*this); } - - // TODO: declare method here instead of scc_io.h - // it is just temporary, until Truc is cleaned up - // friend void write_scc_file(const std::string &outFilePath, - // const Truc &slicer, - // int numberOfParameters, - // int degree, - // bool rivetCompatible, - // bool IgnoreLastGenerators, - // bool stripComments, - // bool reverse); - - private: - MultiFiltrations generator_filtration_values; // defined at construction time. Const - std::vector generator_order; // size fixed at construction time - Structure structure; // defined at construction time. Const - std::vector filtration_container; // filtration of the current slice - PersBackend persistence; // generated by the structure, and generator_order. - -}; // class Truc - -} // namespace truc_interface -} // namespace multiparameter -} // namespace Gudhi diff --git a/multipers/io.pyx b/multipers/io.pyx index 89458fd1..4cc08232 100644 --- a/multipers/io.pyx +++ b/multipers/io.pyx @@ -262,7 +262,6 @@ def function_delaunay_presentation_to_slicer( slicer._build_from_scc_file(path=output_path, shift_dimension=-1 if degree <= 0 else degree-1 ) - def rhomboid_tiling_to_slicer( slicer, point_cloud:np.ndarray, diff --git a/multipers/mma_structures.pxd b/multipers/mma_structures.pxd index 06849157..a2bd3adb 100644 --- a/multipers/mma_structures.pxd +++ b/multipers/mma_structures.pxd @@ -17,7 +17,8 @@ cdef extern from "multiparameter_module_approximation/approximation.h" namespace ctypedef pair[vector[T],vector[T]] interval Summand() except + - Summand(vector[One_critical_filtration[T]]&, vector[One_critical_filtration[T]]&, int) except + nogil + Summand(int) except + + Summand(vector[T]&, vector[T]&, int, int) except + nogil T get_interleaving() nogil T get_local_weight(const vector[T]&, const T) nogil void add_bar(T, T, const vector[T]&, vector[T]&, vector[T]&, const bool, const interval&) nogil @@ -82,7 +83,8 @@ cdef extern from "multiparameter_module_approximation/approximation.h" namespace cdef cppclass Module[T=*]: ctypedef vector[vector[T]] image_type Module() except + nogil - void resize(unsigned int) nogil + Module(const vector[intptr_t]&) + void resize(unsigned int, int) nogil Summand[T]& at(unsigned int) nogil vector[Summand[T]].iterator begin() vector[Summand[T]].iterator end() @@ -103,7 +105,7 @@ cdef extern from "multiparameter_module_approximation/approximation.h" namespace vector[image_type] get_vectorization(const T, const T, const bool, Box[T], unsigned int, unsigned int) nogil MultiDiagram[One_critical_filtration[T], T] get_barcode(Line[T]&, const int, const bool) nogil vector[vector[pair[T,T]]] get_barcode2(Line[T]&, const int) nogil - MultiDiagrams[One_critical_filtration[T],T] get_barcodes(const vector[One_critical_filtration[T]]& , const int, const bool ) nogil + MultiDiagrams[One_critical_filtration[T],T] get_barcodes(const vector[Point[T]]& , const int, const bool ) nogil vector[vector[vector[pair[T,T]]]] get_barcodes2(const vector[Line[T]]& , const int, ) nogil image_type get_landscape(const int,const unsigned int,Box[T],const vector[unsigned int]&) nogil vector[image_type] get_landscapes(const int,const vector[unsigned int],Box[T],const vector[unsigned int]&) nogil @@ -118,8 +120,10 @@ cdef extern from "multiparameter_module_approximation/approximation.h" namespace vector[T] get_interleavings(Box[T]) nogil vector[int] get_degree_splits() nogil void compute_distances_to(T*,vector[vector[T]],bool, int) nogil - + void add_modules_from_ptr(const vector[intptr_t]& modules) + Module[T] permute_summands(const vector[int]&) + Module[T] direct_sum[T](const Module[T]&, const Module[T]&) noexcept nogil diff --git a/multipers/mma_structures.pyx.tp b/multipers/mma_structures.pyx.tp index d22cd7b5..69dfd73e 100644 --- a/multipers/mma_structures.pyx.tp +++ b/multipers/mma_structures.pyx.tp @@ -91,11 +91,11 @@ cdef class PySummand_{{SHORT}}: def get_birth_list(self): cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_birth_list() - return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters()) + return _vff21cview_{{SHORT}}(v, copy = True) def get_death_list(self): cdef vector[One_critical_filtration[{{CTYPE}}]] v = self.sum.get_death_list() - return _vff21cview_{{SHORT}}(v, copy = True, duplicate = self.num_parameters()) + return _vff21cview_{{SHORT}}(v, copy = True) @property def degree(self)->int: return self.sum.get_dimension() @@ -104,10 +104,11 @@ cdef class PySummand_{{SHORT}}: self.sum = summand return self def get_bounds(self): - cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds + cdef pair[vector[{{CTYPE}}],vector[{{CTYPE}}]] cbounds with nogil: cbounds = self.sum.get_bounds().get_bounding_corners() - return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy() + # return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy() + return np.asarray(cbounds.first), np.asarray(cbounds.second) @property def dtype(self): return {{PYTYPE}} @@ -155,8 +156,8 @@ cdef class PyBox_{{SHORT}}: self.box = Box[{{CTYPE}}](bottomCorner, topCorner) @property def num_parameters(self): - cdef size_t dim = self.box.get_lower_corner().num_parameters() - if dim == self.box.get_upper_corner().num_parameters(): return dim + cdef size_t dim = self.box.get_lower_corner().size() + if dim == self.box.get_upper_corner().size(): return dim else: print("Bad box definition.") def contains(self, x): return self.box.contains(x) @@ -200,12 +201,25 @@ cdef class PyModule_{{SHORT}}: for summand in c_other: self.cmod.add_summand(summand, dim) return self + def _add_mmas(self, mmas): + for mma in mmas: + self.merge(mma) + return self + + def permute_summands(self, permutation): + cdef int[:] c_perm = np.asarray(permutation, dtype=np.int32) + other = PyModule_{{SHORT}}() + other.cmod = self.cmod.permute_summands(permutation) + return other def _set_from_ptr(self, intptr_t module_ptr): r""" Copy module from a memory pointer. Unsafe. """ self.cmod = move(dereference((module_ptr))) + def _get_ptr(self): + cdef intptr_t ptr = (&self.cmod) + return ptr def set_box(self, box): assert len(box) == 2, "Box format is [low, hight]" pybox = PyBox_{{SHORT}}(box[0], box[1]) @@ -219,8 +233,8 @@ cdef class PyModule_{{SHORT}}: """ pmodule = PyModule_{{SHORT}}() cdef Box[{{CTYPE}}] c_box = self.cmod.get_box() - pmodule.cmod.set_box(c_box) with nogil: + pmodule.cmod.set_box(c_box) for summand in self.cmod: if summand.get_dimension() == degree: pmodule.cmod.add_summand(summand) @@ -264,8 +278,8 @@ cdef class PyModule_{{SHORT}}: return self.cmod.get_dimension() @property def num_parameters(self)->int: - cdef size_t dim = self.cmod.get_box().get_lower_corner().num_parameters() - assert dim == self.cmod.get_box().get_upper_corner().num_parameters(), "Bad box definition, cannot infer num_parameters." + cdef size_t dim = self.cmod.get_box().get_lower_corner().size() + assert dim == self.cmod.get_box().get_upper_corner().size(), "Bad box definition, cannot infer num_parameters." return dim def dump(self, path:str|None=None): r""" @@ -307,10 +321,11 @@ cdef class PyModule_{{SHORT}}: Computes bounds from the summands' bounds. Useful to change this' box. """ - cdef pair[One_critical_filtration[{{CTYPE}}],One_critical_filtration[{{CTYPE}}]] cbounds + cdef pair[vector[{{CTYPE}}],vector[{{CTYPE}}]] cbounds with nogil: cbounds = self.cmod.get_bounds().get_bounding_corners() - return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy() + # return _ff21cview_{{SHORT}}(&cbounds.first).copy(), _ff21cview_{{SHORT}}(&cbounds.second).copy() + return np.asarray(cbounds.first), np.asarray(cbounds.second) def rescale(self,rescale_factors, int degree=-1): r""" Rescales the fitlration values of the summands by this rescaling vector. @@ -337,7 +352,9 @@ cdef class PyModule_{{SHORT}}: """ if len(self) ==0: return np.empty((self.num_parameters,0)) - values = tuple(tuple(stuff) if len(stuff:=get_summand_filtration_values_{{SHORT}}(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod) + values = tuple( + tuple(stuff) + if len(stuff:=get_summand_filtration_values_{{SHORT}}(summand)) == self.num_parameters else list(stuff) + [[]]*(self.num_parameters - len(stuff)) for summand in self.cmod) try: values = tuple(np.concatenate([ f[parameter] @@ -399,7 +416,8 @@ cdef class PyModule_{{SHORT}}: self.plot(dims[dim_idx],box=box, separated = separated, **kwargs) return corners = self.cmod.get_corners_of_dimension(degree) - plot2d_PyModule(corners, box=box, dimension=degree, **kwargs) + interleavings = self.get_module_of_degree(degree).get_interleavings() + plot2d_PyModule(corners, box=box, dimension=degree, interleavings = interleavings, **kwargs) return def degree_splits(self): return np.asarray(self.cmod.get_degree_splits(), dtype=np.int64) @@ -446,7 +464,7 @@ cdef class PyModule_{{SHORT}}: Structure that holds the barcodes. Barcodes can be retrieved with a .get_points() or a .to_multipers() or a .plot(). """ out = PyMultiDiagram_{{SHORT}}() - out.set(self.cmod.get_barcode(Line[{{CTYPE}}](_py21c_{{SHORT}}(np.asarray(basepoint, dtype={{PYTYPE}}))), degree, threshold)) + out.set(self.cmod.get_barcode(Line[{{CTYPE}}](_py2p_{{SHORT}}(np.asarray(basepoint, dtype={{PYTYPE}}))), degree, threshold)) return out @staticmethod cdef _threshold_bc(bc): @@ -478,10 +496,10 @@ cdef class PyModule_{{SHORT}}: """ basepoint = np.asarray(basepoint, dtype={{PYTYPE}}) if direction is None: - bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint)), degree)) + bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py2p_{{SHORT}}(basepoint)), degree)) else: direction = np.asarray(direction, dtype = {{PYTYPE}}) - bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoint), _py21c_{{SHORT}}(direction)), degree)) + bc = tuple(np.asarray(x).reshape(-1,2) for x in self.cmod.get_barcode2(Line[{{CTYPE}}](_py2p_{{SHORT}}(basepoint), _py2p_{{SHORT}}(direction)), degree)) if not keep_inf: bc = PyModule_{{SHORT}}._threshold_bc(bc) if full: @@ -537,7 +555,7 @@ cdef class PyModule_{{SHORT}}: num=len(basepoints) cdef {{CTYPE}}[:,:] basepoints_view = np.asarray(basepoints, dtype = {{PYTYPE}}) - cdef vector[One_critical_filtration[{{CTYPE}}]] cbasepoints = _py2v1c_{{SHORT}}(basepoints_view) + cdef vector[Point[{{CTYPE}}]] cbasepoints = _py2vp_{{SHORT}}(basepoints_view) out.set(self.cmod.get_barcodes(cbasepoints, degree, threshold)) return out @@ -582,7 +600,7 @@ cdef class PyModule_{{SHORT}}: basepoints = np.asarray(basepoints, dtype={{PYTYPE}}) cdef vector[Line[{{CTYPE}}]] cbasepoints for i in range(num): - cbasepoints.push_back(Line[{{CTYPE}}](_py21c_{{SHORT}}(basepoints[i]))) + cbasepoints.push_back(Line[{{CTYPE}}](_py2p_{{SHORT}}(basepoints[i]))) return tuple(np.asarray(bc) for bc in self.cmod.get_barcodes2(cbasepoints, degree)) def landscape(self, degree:int, k:int=0,box:Sequence|np.ndarray|None=None, resolution:Sequence=[100,100], bool plot=False): @@ -999,16 +1017,17 @@ cdef dump_summand_{{SHORT}}(Summand[{{CTYPE}}]& summand): ) cdef inline Summand[{{CTYPE}}] summand_from_dump_{{SHORT}}(summand_dump): - cdef vector[One_critical_filtration[{{CTYPE}}]] births = _py2v1c_{{SHORT}}(summand_dump[0]) - cdef vector[One_critical_filtration[{{CTYPE}}]] deaths = _py2v1c_{{SHORT}}(summand_dump[1]) + cdef vector[{{CTYPE}}] births = _py2p2_{{SHORT}}(summand_dump[0]) + cdef vector[{{CTYPE}}] deaths = _py2p2_{{SHORT}}(summand_dump[1]) cdef int dim = summand_dump[2] - return Summand[{{CTYPE}}](births,deaths,dim) + return Summand[{{CTYPE}}](births, deaths, summand_dump[0].shape[1], dim) cdef dump_cmod_{{SHORT}}(Module[{{CTYPE}}]& mod): cdef Box[{{CTYPE}}] cbox = mod.get_box() cdef int dim = mod.get_dimension() - cdef cnp.ndarray[{{CTYPE}}, ndim=1] bottom_corner = _ff21cview_{{SHORT}}(&cbox.get_lower_corner()) - cdef cnp.ndarray[{{CTYPE}}, ndim=1] top_corner = _ff21cview_{{SHORT}}(&cbox.get_upper_corner()) + # cannot cast const vector[{{CTYPE}}]& to vector[{{CTYPE}}] without the explicit cast + cdef vector[{{CTYPE}}] bottom_corner = cbox.get_lower_corner() + cdef vector[{{CTYPE}}] top_corner = cbox.get_upper_corner() box = np.asarray([bottom_corner, top_corner]) summands = tuple(dump_summand_{{SHORT}}(summand) for summand in mod) return box, summands diff --git a/multipers/multi_parameter_rank_invariant/diff_helpers.h b/multipers/multi_parameter_rank_invariant/diff_helpers.h index 316adbd4..5a5987d7 100644 --- a/multipers/multi_parameter_rank_invariant/diff_helpers.h +++ b/multipers/multi_parameter_rank_invariant/diff_helpers.h @@ -1,9 +1,10 @@ #pragma once -#include "Simplex_tree_multi_interface.h" #include #include +#include "../gudhi/Simplex_tree_multi_interface.h" + namespace Gudhi { namespace multiparameter { namespace differentiation { @@ -19,7 +20,7 @@ using idx_map_type = std::vector idx_map_type build_idx_map(interface_multi &st, const std::vector &simplices_dimensions) { - auto num_parameters = st.get_number_of_parameters(); + auto num_parameters = st.num_parameters(); if (static_cast(simplices_dimensions.size()) < num_parameters) throw; int max_dim = *std::max_element(simplices_dimensions.begin(), simplices_dimensions.end()); int min_dim = *std::min_element(simplices_dimensions.begin(), simplices_dimensions.end()); diff --git a/multipers/multi_parameter_rank_invariant/euler_characteristic.h b/multipers/multi_parameter_rank_invariant/euler_characteristic.h index 8dc1280b..13980200 100644 --- a/multipers/multi_parameter_rank_invariant/euler_characteristic.h +++ b/multipers/multi_parameter_rank_invariant/euler_characteristic.h @@ -3,12 +3,10 @@ #include #include #include // std::pair -#include "Simplex_tree_multi_interface.h" -#include "multi_parameter_rank_invariant/persistence_slices.h" -// #include - -#include "tensor/tensor.h" +#include "../gudhi/Simplex_tree_multi_interface.h" +#include "../tensor/tensor.h" +#include "persistence_slices.h" namespace Gudhi::multiparameter::euler_characteristic{ @@ -21,11 +19,11 @@ void get_euler_surface( bool mobius_inversion, bool zero_pad ){ - std::vector coordinate_container(st_multi.get_number_of_parameters()); + std::vector coordinate_container(st_multi.num_parameters()); for (auto sh : st_multi.complex_simplex_range()){ const auto& multi_filtration = st_multi.filtration(sh); - for (index_type i=0u; i(multi_filtration[i]); + for (index_type i=0u; i(multi_filtration(0,i)); } int sign = 1-2*(st_multi.dimension(sh) % 2); if (mobius_inversion && zero_pad) diff --git a/multipers/multi_parameter_rank_invariant/function_rips.h b/multipers/multi_parameter_rank_invariant/function_rips.h index c7952a08..e8427230 100644 --- a/multipers/multi_parameter_rank_invariant/function_rips.h +++ b/multipers/multi_parameter_rank_invariant/function_rips.h @@ -1,21 +1,22 @@ #pragma once -#include "Simplex_tree_multi_interface.h" -#include "multi_parameter_rank_invariant/persistence_slices.h" -#include "tensor/tensor.h" -#include -#include #include #include +#include "../gudhi/Simplex_tree_multi_interface.h" +#include "../gudhi/Persistence_slices_interface.h" +#include "../tensor/tensor.h" +#include "persistence_slices.h" + namespace Gudhi { namespace multiparameter { namespace function_rips { using value_type = typename python_interface::interface_std::Filtration_value; -using _multifiltration = typename Gudhi::multi_filtration::One_critical_filtration; -using _multist = python_interface::Simplex_tree_multi_interface<_multifiltration>; -using interface_multi = _multist; +using _multifiltration = multipers::tmp_interface::Filtration_value; +using _multi_st = python_interface::Simplex_tree_multi_interface<_multifiltration>; +using mult_opt = Gudhi::multi_persistence::Simplex_tree_options_multidimensional_filtration<_multifiltration>; +using interface_multi = _multi_st; std::pair, std::vector> @@ -39,109 +40,91 @@ inline radius_to_coordinate(Simplex_tree_std &st) { // Takes a standard simplextree and turn it into a simplextreemulti, whose first // axis is the rips, and the others are the filtrations of the node at each // degree in degrees Assumes that the degrees are sorted, and unique - -std::tuple<_multist, - std::vector, int> -inline get_degree_filtrations( // also return max_degree,filtration_values - python_interface::interface_std &st, const std::vector °rees) { +// also return max_degree,filtration_values +inline std::tuple<_multi_st, std::vector, int> get_degree_filtrations(python_interface::interface_std &st, + const std::vector °rees) { constexpr const bool verbose = false; using filtration_lists = std::vector>; - assert(st.dimension() == - 1); // the st slices will be expanded + collapsed after being filled. - _multist st_multi; - std::vector rips_filtration_values = { - 0}; // vector that will hold the used filtration values + assert(st.dimension() == 1); // the st slices will be expanded + collapsed after being filled. + std::vector rips_filtration_values = {0}; // vector that will hold the used filtration values rips_filtration_values.reserve(st.num_simplices()); - int max_st_degree = 0; + unsigned int max_st_degree = 0; + + unsigned int num_degrees = degrees.size(); + // puts the st filtration in axis 0 + fitrations for each degrees afterward + _multifiltration default_f(static_cast(num_degrees), 0); + _multi_st st_multi(Gudhi::multi_persistence::make_multi_dimensional(st, default_f, 0)); - int num_degrees = degrees.size(); - Gudhi::multi_persistence::multify(st, st_multi, 0); // puts the st filtration in axis 0 + fitrations for - // each degrees afterward // preprocess filtration_lists edge_filtration_of_nodes(st.num_vertices()); for (auto sh : st.complex_simplex_range()) { - if (st.dimension(sh) == 0) - continue; + if (st.dimension(sh) == 0) continue; value_type filtration = st.filtration(sh); for (auto node : st.simplex_vertex_range(sh)) { edge_filtration_of_nodes[node].push_back(filtration); } } - for (auto &filtrations : edge_filtration_of_nodes) { // todo : parallel ? + for (auto &filtrations : edge_filtration_of_nodes) { // todo : parallel ? std::sort(filtrations.begin(), filtrations.end()); - int node_degree = filtrations.size(); + unsigned int node_degree = filtrations.size(); max_st_degree = std::max(node_degree, max_st_degree); filtrations.resize(std::max(num_degrees, node_degree)); - if constexpr (verbose) - std::cout << "Filtration of node "; - for (auto degree_index = 0; degree_index < num_degrees; degree_index++) { - if (degrees[degree_index] < node_degree) + if constexpr (verbose) std::cout << "Filtration of node "; + for (unsigned int degree_index = 0; degree_index < num_degrees; degree_index++) { + if (degrees[degree_index] < static_cast(node_degree)) filtrations[degree_index] = filtrations[degrees[degree_index]]; else filtrations[degree_index] = std::numeric_limits::infinity(); - if constexpr (verbose) - std::cout << filtrations[degree_index] << " "; + if constexpr (verbose) std::cout << filtrations[degree_index] << " "; } filtrations.resize(num_degrees); std::reverse(filtrations.begin(), - filtrations.end()); // degree is in opposite direction + filtrations.end()); // degree is in opposite direction for (value_type filtration_value : filtrations) - rips_filtration_values.push_back( - filtration_value); // we only do that here to have a smaller grid. - if constexpr (verbose) - std::cout << "\n"; + rips_filtration_values.push_back(filtration_value); // we only do that here to have a smaller grid. + if constexpr (verbose) std::cout << "\n"; } // sort + unique the filtration values std::sort(rips_filtration_values.begin(), rips_filtration_values.end()); - rips_filtration_values.erase( - std::unique(rips_filtration_values.begin(), rips_filtration_values.end()), - rips_filtration_values.end()); + rips_filtration_values.erase(std::unique(rips_filtration_values.begin(), rips_filtration_values.end()), + rips_filtration_values.end()); // fills the degree_rips simplextree with lower star - auto sh_standard = - st.complex_simplex_range() - .begin(); // waiting for c++23 & zip to remove this garbage + auto sh_standard = st.complex_simplex_range().begin(); // waiting for c++23 & zip to remove this garbage auto _end = st.complex_simplex_range().end(); auto sh_multi = st_multi.complex_simplex_range().begin(); for (; sh_standard != _end; ++sh_multi, ++sh_standard) { - if (st.dimension(*sh_standard) == 0) // only fill using the node - continue; // will be filled afterward + // only fill using the node + // will be filled afterward + if (st.dimension(*sh_standard) == 0) continue; // dimension is 1 by assumption. fill using the node + rips value value_type edge_filtration = st.filtration(*sh_standard); - _multifiltration &edge_degree_rips_filtration = - st_multi.filtration_mutable(*sh_multi); // the filtration vector to fill - edge_degree_rips_filtration.resize(num_degrees); - for (auto degree_index = 0; degree_index < num_degrees; degree_index++) { - value_type edge_filtration_of_degree = - edge_filtration; // copy as we do the max with edges of degree index + // the filtration vector to fill + _multifiltration &edge_degree_rips_filtration = st_multi.get_filtration_value(*sh_multi); + for (unsigned int degree_index = 0; degree_index < num_degrees; degree_index++) { + value_type edge_filtration_of_degree = edge_filtration; // copy as we do the max with edges of degree index for (int node : st.simplex_vertex_range(*sh_standard)) { - edge_filtration_of_degree = - std::max(edge_filtration_of_degree, - edge_filtration_of_nodes[node][degree_index]); + edge_filtration_of_degree = std::max(edge_filtration_of_degree, edge_filtration_of_nodes[node][degree_index]); } - edge_degree_rips_filtration[degree_index] = - edge_filtration_of_degree; // fills the correct value in the edge - // filtration + // fills the correct value in the edge filtration + edge_degree_rips_filtration(0, degree_index) = edge_filtration_of_degree; } } // fills the dimension 0 simplices - { // scope for count; - for (auto vertex : - st_multi.complex_vertex_range()) { // should be in increasing order - auto &vertex_filtration = - st_multi.filtration_mutable(st_multi.find({vertex})); + { // scope for count; + for (auto vertex : st_multi.complex_vertex_range()) { // should be in increasing order + auto &vertex_filtration = st_multi.get_filtration_value(st_multi.find({vertex})); if constexpr (verbose) { std::cout << "Setting filtration of node " << vertex << " to "; - for (auto degree_index = 0u; degree_index < num_degrees; - degree_index++) { + for (auto degree_index = 0u; degree_index < num_degrees; degree_index++) { std::cout << edge_filtration_of_nodes[vertex][degree_index] << " "; } std::cout << "\n"; } - vertex_filtration.swap(edge_filtration_of_nodes[vertex]); + vertex_filtration = _multifiltration(edge_filtration_of_nodes[vertex].begin(), edge_filtration_of_nodes[vertex].end()); } } @@ -150,21 +133,19 @@ inline get_degree_filtrations( // also return max_degree,filtration_values // assumes that the degree is 1 inline void fill_st_slice(Simplex_tree_std &st_container, - _multist °ree_rips_st, int degree) { + _multi_st °ree_rips_st, int degree) { auto sh_std = st_container.complex_simplex_range().begin(); auto sh_multi = degree_rips_st.complex_simplex_range().begin(); auto sh_end = st_container.complex_simplex_range().end(); for (; sh_std != sh_end; ++sh_std, ++sh_multi) { - value_type splx_filtration = - degree_rips_st.filtration_mutable(*sh_multi)[degree]; + value_type splx_filtration = degree_rips_st.get_filtration_value(*sh_multi)(0, degree); st_container.assign_filtration(*sh_std, splx_filtration); } - return; } template inline void -compute_2d_function_rips(_multist &st_multi, // Function rips +compute_2d_function_rips(_multi_st &st_multi, // Function rips // Simplex_tree_std &_st, const tensor::static_tensor_view &out, // assumes its a zero tensor @@ -176,9 +157,9 @@ compute_2d_function_rips(_multist &st_multi, // Function rips std::cout << "Grid shape : " << I << " " << J << std::endl; // inits default simplextrees - Simplex_tree_std _st; - Gudhi::multi_persistence::flatten(_st, st_multi, - -1); // copies the st_multi to a standard 1-pers simplextree + // copies the st_multi to a standard 1-pers simplextree, and puts its filtration values to 0 for all. + Simplex_tree_std _st( + st_multi, []([[maybe_unused]] const _multifiltration &f) -> Simplex_tree_std::Filtration_value { return 0.; }); tbb::enumerable_thread_specific thread_simplex_tree(_st); int max_simplex_dimension = *std::max_element(degrees.begin(), degrees.end()) + 1; @@ -258,7 +239,7 @@ inline get_degree_rips_st_python(const intptr_t simplextree_ptr, const std::vector °rees) { auto &st_std = python_interface::get_simplextree_from_pointer(simplextree_ptr); auto &st_multi_python_container = - python_interface::get_simplextree_from_pointer<_multist>(st_multi_ptr); + python_interface::get_simplextree_from_pointer<_multi_st>(st_multi_ptr); auto [st_multi, rips_filtration_values, max_node_degree] = get_degree_filtrations(st_std, degrees); st_multi_python_container = std::move(st_multi); diff --git a/multipers/multi_parameter_rank_invariant/hilbert_function.h b/multipers/multi_parameter_rank_invariant/hilbert_function.h index d94f6801..74dd6c18 100644 --- a/multipers/multi_parameter_rank_invariant/hilbert_function.h +++ b/multipers/multi_parameter_rank_invariant/hilbert_function.h @@ -1,11 +1,6 @@ #pragma once -#include "Simplex_tree_multi_interface.h" -#include "multi_parameter_rank_invariant/persistence_slices.h" -#include "tensor/tensor.h" #include -#include -#include #include #include #include @@ -14,6 +9,11 @@ #include // std::pair #include +#include "../gudhi/Simplex_tree_multi_interface.h" +#include "../gudhi/gudhi/Slicer.h" +#include "../tensor/tensor.h" +#include "persistence_slices.h" + namespace Gudhi { namespace multiparameter { namespace hilbert_function { @@ -21,21 +21,21 @@ namespace hilbert_function { // TODO : this function is ugly template inline typename Filtration::value_type horizontal_line_filtration2(const Filtration &x, + unsigned int gen_index, indices_type height, indices_type i, indices_type j, const std::vector &fixed_values) { - const auto &inf = Filtration::Generator::T_inf; - for (indices_type k = 0u; k < static_cast(x.size()); k++) { + const auto &inf = Filtration::T_inf; + for (indices_type k = 0u; k < static_cast(x.num_parameters()); k++) { if (k == i || k == j) continue; // coordinate in the plane - if (x[k] > fixed_values[k]) // simplex appears after the plane + if (x(gen_index, k) > fixed_values[k]) // simplex appears after the plane return inf; } - if (x[j] <= height) // simplex apppears in the plane, but is it in the line - // with height "height" - return x[i]; - else - return inf; + // simplex appears in the plane, but is it in the line with height "height" + if (x(gen_index, j) <= height) + return x(gen_index, i); + return inf; } template @@ -56,12 +56,12 @@ inline void compute_2d_hilbert_surface( bool zero_pad, int expand_collapse_dim = 0) { using value_type = typename Filtration::value_type; - // if (grid_shape.size() < 2 || st_multi.get_number_of_parameters() < 2) + // if (grid_shape.size() < 2 || st_multi.num_parameters() < 2) // throw std::invalid_argument("Grid shape has to have at least 2 - // element."); if (st_multi.get_number_of_parameters() - fixed_values.size() + // element."); if (st_multi.num_parameters() - fixed_values.size() // != 2) throw std::invalid_argument("Fix more values for the // simplextree, which has a too big number of parameters"); - // assert(fixed_values.size() == st_multi.get_number_of_parameters()); + // assert(fixed_values.size() == st_multi.num_parameters()); constexpr const bool verbose = false; index_type I = grid_shape[i + 1], J = grid_shape[j + 1]; @@ -108,7 +108,7 @@ inline void compute_2d_hilbert_surface( coordinates_container[j + 1] = height; - Filtration multi_filtration(st_multi.get_number_of_parameters()); + // Filtration multi_filtration(st_multi.num_parameters()); auto sh_standard = st_std.complex_simplex_range().begin(); auto _end = st_std.complex_simplex_range().end(); auto sh_multi = st_multi.complex_simplex_range().begin(); @@ -116,23 +116,20 @@ inline void compute_2d_hilbert_surface( // for (auto [sh_standard, sh_multi] : // std::ranges::views::zip(st_std.complex_simplex_range(), // st_multi.complex_simplex_range())){ // too bad apple clang exists - multi_filtration = st_multi.filtration(*sh_multi); + Filtration multi_filtration = st_multi.filtration(*sh_multi); typename Simplex_tree_std::Filtration_value horizontal_filtration; - if constexpr (Filtration::is_multi_critical) { - horizontal_filtration = std::numeric_limits::infinity(); - for (const auto &stuff : multi_filtration) - horizontal_filtration = std::min(horizontal_filtration, - static_cast( - horizontal_line_filtration2(stuff, height, i, j, fixed_values))); - } else { - horizontal_filtration = horizontal_line_filtration2(multi_filtration, height, i, j, fixed_values); + horizontal_filtration = std::numeric_limits::infinity(); + for (unsigned int g = 0; g < multi_filtration.num_generators(); ++g) { + horizontal_filtration = std::min(horizontal_filtration, + static_cast(horizontal_line_filtration2( + multi_filtration, g, height, i, j, fixed_values))); } st_std.assign_filtration(*sh_standard, horizontal_filtration); if constexpr (verbose) { - Gudhi::multi_filtration::One_critical_filtration splx; - for (auto vertex : st_multi.simplex_vertex_range(*sh_multi)) splx.push_back(vertex); - std::cout << "Simplex " << splx << "/" << st_std.num_simplices() << " Filtration multi " + std::cout << "Simplex {"; + for (auto vertex : st_multi.simplex_vertex_range(*sh_multi)) std::cout << vertex << " "; + std::cout << "} / " << st_std.num_simplices() << " Filtration multi " << st_multi.filtration(*sh_multi) << " Filtration 1d " << st_std.filtration(*sh_standard) << "\n"; } } @@ -173,7 +170,7 @@ inline void compute_2d_hilbert_surface( dtype *ptr = &out[coordinates_container]; auto stop_value = death > static_cast(border) ? border : static_cast(death); // Warning : for some reason linux static casts float inf to -min_int - // so min doesnt work. + // so min doesn't work. if constexpr (verbose) { std::cout << "Adding : ("; for (auto stuff : coordinates_container) std::cout << stuff << ", "; @@ -300,10 +297,9 @@ void get_hilbert_surface(python_interface::Simplex_tree_multi_interface coordinates_container(st_multi.get_number_of_parameters() + 1); // +1 for degree + // copies the st_multi to a standard 1-pers simplextree without the filtration values + Simplex_tree_std _st(st_multi, []([[maybe_unused]] const Filtration &f) -> Simplex_tree_std::Filtration_value { return 0.; }); + std::vector coordinates_container(st_multi.num_parameters() + 1); // +1 for degree // coordinates_container.reserve(fixed_values.size()+1); // coordinates_container.push_back(0); // degree // for (auto c : fixed_values) coordinates_container.push_back(c); @@ -339,12 +335,12 @@ std::pair>, std::vector> get_hilber // auto &st_multi = // get_simplextree_from_pointer>(simplextree_ptr); tensor::static_tensor_view container(data_ptr, grid_shape); // assumes its a zero tensor - std::vector coordinates_to_compute(st_multi.get_number_of_parameters()); + std::vector coordinates_to_compute(st_multi.num_parameters()); for (auto i = 0u; i < coordinates_to_compute.size(); i++) coordinates_to_compute[i] = i; // for (auto [c,i] : std::views::zip(coordinates_to_compute, - // std::views::iota(0,st_multi.get_number_of_parameters()))) c=i; // NIK apple + // std::views::iota(0,st_multi.num_parameters()))) c=i; // NIK apple // clang - std::vector fixed_values(st_multi.get_number_of_parameters()); + std::vector fixed_values(st_multi.num_parameters()); if (verbose) { std::cout << "Container shape : "; @@ -354,9 +350,9 @@ std::pair>, std::vector> get_hilber } if (zero_pad) { // +1 is bc degree is on first axis. - for (auto i = 1; i < st_multi.get_number_of_parameters() + 1; i++) + for (auto i = 1; i < st_multi.num_parameters() + 1; i++) grid_shape[i]--; // get hilbert surface computes according to grid_shape. - // for (auto i : std::views::iota(1,st_multi.get_number_of_parameters()+1)) + // for (auto i : std::views::iota(1,st_multi.num_parameters()+1)) // grid_shape[i]--; // get hilbert surface computes according to grid_shape. } @@ -379,9 +375,9 @@ std::pair>, std::vector> get_hilber } // for (indices_type axis : - // std::views::iota(2,st_multi.get_number_of_parameters()+1)) // +1 for the + // std::views::iota(2,st_multi.num_parameters()+1)) // +1 for the // degree in axis 0 - for (indices_type axis = 2u; axis < st_multi.get_number_of_parameters() + 1; axis++) container.differentiate(axis); + for (indices_type axis = 2u; axis < st_multi.num_parameters() + 1; axis++) container.differentiate(axis); if (verbose) { std::cout << "Done.\n"; std::cout << "Sparsifying the measure ..." << std::flush; @@ -408,12 +404,12 @@ void get_hilbert_surface_python(python_interface::Simplex_tree_multi_interface>(simplextree_ptr); tensor::static_tensor_view container(data_ptr, grid_shape); // assumes its a zero tensor - std::vector coordinates_to_compute(st_multi.get_number_of_parameters()); + std::vector coordinates_to_compute(st_multi.num_parameters()); for (auto i = 0u; i < coordinates_to_compute.size(); i++) coordinates_to_compute[i] = i; // for (auto [c,i] : std::views::zip(coordinates_to_compute, - // std::views::iota(0,st_multi.get_number_of_parameters()))) c=i; // NIK apple + // std::views::iota(0,st_multi.num_parameters()))) c=i; // NIK apple // clang - std::vector fixed_values(st_multi.get_number_of_parameters()); + std::vector fixed_values(st_multi.num_parameters()); if (verbose) { std::cout << "Container shape : "; @@ -423,9 +419,9 @@ void get_hilbert_surface_python(python_interface::Simplex_tree_multi_interface +template inline void compute_2d_hilbert_surface( - tbb::enumerable_thread_specific::ThreadSafe, + tbb::enumerable_thread_specific::Thread_safe, std::vector>> &thread_stuff, const tensor::static_tensor_view &out, // assumes its a zero tensor const std::vector grid_shape, @@ -476,21 +472,16 @@ inline void compute_2d_hilbert_surface( coordinates_container[j + 1] = height; - auto &slice_filtration = slicer.get_one_filtration(); - const auto &multi_filtration = slicer.get_filtrations(); + auto &slice_filtration = slicer.get_slice(); + const auto &multi_filtration = slicer.get_filtration_values(); for (std::size_t k = 0; k < multi_filtration.size(); k++) { - value_type horizontal_filtration; - if constexpr (Filtration::is_multi_critical) { - horizontal_filtration = Filtration::Generator::T_inf; - for (const auto &stuff : multi_filtration[k]) - horizontal_filtration = - std::min(horizontal_filtration, - static_cast(horizontal_line_filtration2(stuff, height, i, j, fixed_values))); - } else { - horizontal_filtration = horizontal_line_filtration2(multi_filtration[k], height, i, j, fixed_values); + slice_filtration[k] = Filtration::T_inf; + for (unsigned int g = 0; g < multi_filtration[k].num_generators(); ++g) { + slice_filtration[k] = std::min(slice_filtration[k], + static_cast(horizontal_line_filtration2( + multi_filtration[k], g, height, i, j, fixed_values))); } - slice_filtration[k] = horizontal_filtration; } if constexpr (verbose) { @@ -498,24 +489,27 @@ inline void compute_2d_hilbert_surface( for (auto stuff : fixed_values) std::cout << stuff << " "; std::cout << "]" << std::endl; } - using bc_type = typename truc_interface::Truc::split_barcode; + + using bc_type = typename Gudhi::multi_persistence::Slicer::template Multi_dimensional_flat_barcode<>; + if constexpr (PersBackend::is_vine) { - if (!slicer.has_persistence()) [[unlikely]] { - slicer.compute_persistence(); + if (!slicer.persistence_computation_is_initialized()) [[unlikely]] { + slicer.initialize_persistence_computation(); } else { slicer.vineyard_update(); } } else { - slicer.compute_persistence(ignore_inf); + slicer.initialize_persistence_computation(ignore_inf); } - bc_type barcodes = slicer.get_barcode(); + + bc_type barcodes = slicer.template get_flat_barcode(); index_type degree_index = 0; for (auto degree : degrees) { // TODO range view cartesian product const auto &barcode = barcodes[degree]; coordinates_container[0] = degree_index; for (const auto &bar : barcode) { - auto birth = bar.first; // float - auto death = bar.second; + auto birth = bar[0]; // float + auto death = bar[1]; if (birth > I) // some birth can be infinite continue; @@ -567,9 +561,9 @@ inline void compute_2d_hilbert_surface( return; } -template +template void _rec_get_hilbert_surface( - tbb::enumerable_thread_specific::ThreadSafe, + tbb::enumerable_thread_specific::Thread_safe, std::vector>> &thread_stuff, const tensor::static_tensor_view &out, // assumes its a zero tensor const std::vector grid_shape, @@ -591,7 +585,7 @@ void _rec_get_hilbert_surface( std::cout << ")." << std::endl; } if (coordinates_to_compute.size() == 2) { - compute_2d_hilbert_surface(thread_stuff, + compute_2d_hilbert_surface(thread_stuff, out, // assumes its a zero tensor grid_shape, degrees, @@ -610,7 +604,7 @@ void _rec_get_hilbert_surface( // Updates fixes values that defines the slice std::vector _fixed_values = fixed_values; // TODO : do not copy this //thread local _fixed_values[coordinate_to_iterate] = z; - _rec_get_hilbert_surface( + _rec_get_hilbert_surface( thread_stuff, out, grid_shape, degrees, coordinates_to_compute, _fixed_values, mobius_inverion, zero_pad, ignore_inf); }); // rmq : with mobius_inversion + rec, the coordinates to compute size is 2 => @@ -618,8 +612,8 @@ void _rec_get_hilbert_surface( // => inversion is only needed for coords > 2 } -template -void get_hilbert_surface(truc_interface::Truc &slicer, +template +void get_hilbert_surface(Gudhi::multi_persistence::Slicer &slicer, const tensor::static_tensor_view &out, // assumes its a zero tensor const std::vector &grid_shape, const std::vector °rees, @@ -633,9 +627,9 @@ void get_hilbert_surface(truc_interface::Truc::ThreadSafe; + using ThreadSafe = typename Gudhi::multi_persistence::Slicer::Thread_safe; ThreadSafe slicer_thread(slicer); - std::vector coordinates_container(slicer_thread.num_parameters() + 1); // +1 for degree + std::vector coordinates_container(slicer_thread.get_number_of_parameters() + 1); // +1 for degree // coordinates_container.reserve(fixed_values.size()+1); // coordinates_container.push_back(0); // degree // for (auto c : fixed_values) coordinates_container.push_back(c); @@ -643,17 +637,16 @@ void get_hilbert_surface(truc_interface::Truc>> thread_stuff( thread_data_initialization); // this has a fixed size, so // init should be benefic - _rec_get_hilbert_surface( + _rec_get_hilbert_surface( thread_stuff, out, grid_shape, degrees, coordinates_to_compute, fixed_values, mobius_inverion, zero_pad, ignore_inf); } template -void get_hilbert_surface_python(truc_interface::Truc &slicer, +void get_hilbert_surface_python(Gudhi::multi_persistence::Slicer &slicer, dtype *data_ptr, std::vector grid_shape, const std::vector degrees, @@ -667,11 +660,11 @@ void get_hilbert_surface_python(truc_interface::Truc>(simplextree_ptr); tensor::static_tensor_view container(data_ptr, grid_shape); // assumes its a zero tensor - int num_parameters = slicer.num_parameters(); + int num_parameters = slicer.get_number_of_parameters(); std::vector coordinates_to_compute(num_parameters); for (auto i = 0u; i < coordinates_to_compute.size(); i++) coordinates_to_compute[i] = i; // for (auto [c,i] : std::views::zip(coordinates_to_compute, - // std::views::iota(0,st_multi.get_number_of_parameters()))) c=i; // NIK apple + // std::views::iota(0,st_multi.num_parameters()))) c=i; // NIK apple // clang std::vector fixed_values(num_parameters); @@ -685,7 +678,7 @@ void get_hilbert_surface_python(truc_interface::Truc std::pair>, std::vector> get_hilbert_signed_measure( - truc_interface::Truc &slicer, + Gudhi::multi_persistence::Slicer &slicer, dtype *data_ptr, std::vector grid_shape, const std::vector degrees, @@ -720,12 +712,12 @@ std::pair>, std::vector> get_hilber // auto &st_multi = // get_simplextree_from_pointer>(simplextree_ptr); tensor::static_tensor_view container(data_ptr, grid_shape); // assumes its a zero tensor - std::vector coordinates_to_compute(slicer.num_parameters()); + std::vector coordinates_to_compute(slicer.get_number_of_parameters()); for (auto i = 0u; i < coordinates_to_compute.size(); i++) coordinates_to_compute[i] = i; // for (auto [c,i] : std::views::zip(coordinates_to_compute, - // std::views::iota(0,st_multi.get_number_of_parameters()))) c=i; // NIK apple + // std::views::iota(0,st_multi.num_parameters()))) c=i; // NIK apple // clang - std::vector fixed_values(slicer.num_parameters()); + std::vector fixed_values(slicer.get_number_of_parameters()); if (verbose) { std::cout << "Container shape : "; @@ -735,9 +727,9 @@ std::pair>, std::vector> get_hilber } if (zero_pad) { // +1 is bc degree is on first axis. - for (auto i = 1u; i < slicer.num_parameters() + 1; i++) + for (auto i = 1u; i < slicer.get_number_of_parameters() + 1; i++) grid_shape[i]--; // get hilbert surface computes according to grid_shape. - // for (auto i : std::views::iota(1,st_multi.get_number_of_parameters()+1)) + // for (auto i : std::views::iota(1,st_multi.num_parameters()+1)) // grid_shape[i]--; // get hilbert surface computes according to grid_shape. } @@ -752,9 +744,9 @@ std::pair>, std::vector> get_hilber } // for (indices_type axis : - // std::views::iota(2,st_multi.get_number_of_parameters()+1)) // +1 for the + // std::views::iota(2,st_multi.num_parameters()+1)) // +1 for the // degree in axis 0 - for (indices_type axis = 2; axis < static_cast(slicer.num_parameters() + 1); axis++) + for (indices_type axis = 2; axis < static_cast(slicer.get_number_of_parameters() + 1); axis++) container.differentiate(axis); if (verbose) { std::cout << "Done.\n"; diff --git a/multipers/multi_parameter_rank_invariant/persistence_slices.h b/multipers/multi_parameter_rank_invariant/persistence_slices.h index 2b9974e6..7978b0c1 100644 --- a/multipers/multi_parameter_rank_invariant/persistence_slices.h +++ b/multipers/multi_parameter_rank_invariant/persistence_slices.h @@ -1,25 +1,21 @@ #pragma once -#include "gudhi/Flag_complex_edge_collapser.h" -#include -#include -#include +#include +#include "../gudhi/gudhi/Flag_complex_edge_collapser.h" +#include "../gudhi/gudhi/Persistent_cohomology.h" +#include "../gudhi/gudhi/Multi_persistence/Box.h" +#include "../gudhi/gudhi/Simplex_tree/simplex_tree_options.h" +#include "../gudhi/gudhi/Simplex_tree.h" namespace Gudhi { namespace multiparameter { -struct Simplex_tree_float { // smaller simplextrees - typedef linear_indexing_tag Indexing_tag; +struct Simplex_tree_float : Gudhi::Simplex_tree_options_default { // smaller simplextrees typedef std::int32_t Vertex_handle; typedef float Filtration_value; - typedef std::uint32_t Simplex_key; - static const bool store_key = true; - static const bool store_filtration = true; - static const bool contiguous_vertices = false; // TODO OPTIMIZATION : maybe make the simplextree contiguous when - // calling grid_squeeze ? + // TODO: OPTIMIZATION, maybe make the simplextree contiguous when calling grid_squeeze ? + // static const bool contiguous_vertices = true; static const bool link_nodes_by_label = true; - static const bool stable_simplex_handles = false; - static const bool is_multi_parameter = false; }; // using Simplex_tree_float = Simplex_tree_options_fast_persistence; @@ -50,9 +46,10 @@ inline std::vector compute_dgms(interface_std_like &st, const std::vector °rees, int num_collapses, int expansion_dim) { + std::vector out(degrees.size()); - static_assert(!interface_std_like::Options::is_multi_parameter, - "Can only compute persistence for 1-parameter simplextrees."); + // static_assert(!interface_std_like::Options::is_multi_parameter, + // "Can only compute persistence for 1-parameter simplextrees."); const bool verbose = false; if (num_collapses > 0) { auto collapsed_st = collapse_edges(st, num_collapses); @@ -65,8 +62,12 @@ inline std::vector compute_dgms(interface_std_like &st, else if (expansion_dim > 0) { st.expansion(expansion_dim); } - - st.initialize_filtration(true); // true is ignore_infinite_values + tbb::task_arena arena(1); + arena.execute( + [&]{ + st.initialize_filtration(true); // true is ignore_infinite_values + } + ); constexpr int coeff_field_characteristic = 11; constexpr typename interface_std_like::Filtration_value min_persistence = 0; diff --git a/multipers/multi_parameter_rank_invariant/rank_invariant.h b/multipers/multi_parameter_rank_invariant/rank_invariant.h index 6ccd8f8a..7d2874f4 100644 --- a/multipers/multi_parameter_rank_invariant/rank_invariant.h +++ b/multipers/multi_parameter_rank_invariant/rank_invariant.h @@ -1,21 +1,22 @@ #pragma once -#include -#include "gudhi/truc.h" -#include "multi_parameter_rank_invariant/persistence_slices.h" -#include "tensor/tensor.h" + #include #include -#include #include #include #include #include // std::pair #include +#include "../gudhi/gudhi/multi_simplex_tree_helpers.h" +#include "../gudhi/gudhi/Slicer.h" +#include "../tensor/tensor.h" +#include "persistence_slices.h" + namespace Gudhi { namespace multiparameter { namespace rank_invariant { -using Index = truc_interface::index_type; +// using Index = truc_interface::index_type; // using Elbow = std::vector>;grid template @@ -74,18 +75,11 @@ inline void compute_2d_rank_invariant_of_elbow( auto sh_multi = st_multi.complex_simplex_range().begin(); for (; sh_standard != _end; ++sh_multi, ++sh_standard) { const Filtration &multi_filtration = st_multi.filtration(*sh_multi); - value_type filtration_in_slice; - if constexpr (Filtration::is_multi_critical) { - filtration_in_slice = inf; - for (const auto &stuff : multi_filtration) { - value_type x = stuff[0]; - value_type y = stuff[1]; - filtration_in_slice = std::min(filtration_in_slice, get_slice_rank_filtration(x, y, I, J)); - } - } else { - value_type x = multi_filtration[0]; - value_type y = multi_filtration[1]; - filtration_in_slice = get_slice_rank_filtration(x, y, I, J); + value_type filtration_in_slice = inf; + for (unsigned int g = 0; g < multi_filtration.num_generators(); ++g){ + value_type x = multi_filtration(g, 0); + value_type y = multi_filtration(g, 1); + filtration_in_slice = std::min(filtration_in_slice, get_slice_rank_filtration(x, y, I, J)); } _st_container.assign_filtration(*sh_standard, filtration_in_slice); } @@ -124,10 +118,9 @@ inline void compute_2d_rank_invariant( const std::vector °rees, bool expand_collapse) { if (degrees.size() == 0) return; - assert(st_multi.get_number_of_parameters() == 2); - Simplex_tree_std st_; - Gudhi::multi_persistence::flatten(st_, st_multi, - 0); // copies the st_multi to a standard 1-pers simplextree + assert(st_multi.num_parameters() == 2); + // copies the st_multi to a standard 1-pers simplextree + Simplex_tree_std st_ = Gudhi::multi_persistence::make_one_dimensional(st_multi, 0); const int max_dim = expand_collapse ? *std::max_element(degrees.begin(), degrees.end()) + 1 : 0; index_type X = grid_shape[1]; index_type Y = grid_shape[2]; // First axis is degree @@ -162,12 +155,11 @@ void compute_rank_invariant_python( } template , + class MultiFiltration, typename dtype, typename index_type> inline void compute_2d_rank_invariant_of_elbow( - typename truc_interface::Truc::ThreadSafe &slicer, // truc slicer + typename Gudhi::multi_persistence::Slicer::Thread_safe &slicer, // truc slicer const tensor::static_tensor_view &out, // assumes its a zero tensor const index_type I, const index_type J, @@ -178,7 +170,7 @@ inline void compute_2d_rank_invariant_of_elbow( const bool flip_death = false, const bool ignore_inf = true) { using value_type = typename MultiFiltration::value_type; - const auto &filtrations_values = slicer.get_filtrations(); + const auto &filtrations_values = slicer.get_filtration_values(); auto num_generators = filtrations_values.size(); // one_persistence.resize(num_generators); // local variable should be // initialized correctly @@ -187,32 +179,25 @@ inline void compute_2d_rank_invariant_of_elbow( if constexpr (verbose) std::cout << "filtration_in_slice : [ "; for (auto i = 0u; i < num_generators; ++i) { const auto &f = filtrations_values[i]; - value_type filtration_in_slice = MultiFiltration::Generator::T_inf; - if constexpr (MultiFiltration::is_multi_critical) { - for (const auto &stuff : f) { - value_type x = stuff[0]; - value_type y = stuff[1]; + value_type filtration_in_slice = MultiFiltration::T_inf; + for (unsigned int g = 0; g < f.num_generators(); ++g) { + value_type x = f(g,0); + value_type y = f(g,1); - filtration_in_slice = std::min(filtration_in_slice, get_slice_rank_filtration(x, y, I, J)); - } - } else { - value_type x = f[0]; - value_type y = f[1]; - filtration_in_slice = get_slice_rank_filtration(x, y, I, J); + filtration_in_slice = std::min(filtration_in_slice, get_slice_rank_filtration(x, y, I, J)); } if constexpr (verbose) std::cout << filtration_in_slice << ","; - slicer.get_one_filtration()[i] = filtration_in_slice; + slicer.get_slice()[i] = filtration_in_slice; } if constexpr (verbose) std::cout << "\b]" << std::endl; index_type degree_index = 0; // order_container.resize(slicer.num_generators()); // local variable should // be initialized correctly - // TODO : use slicer::ThreadSafe instead of maintaining one_pers & order + // TODO : use slicer::Thread_safe instead of maintaining one_pers & order // BUG : This will break as soon as slicer interface change - using bc_type = typename truc_interface::Truc::split_barcode; - bc_type barcodes; + using bc_type = typename Gudhi::multi_persistence::Slicer::template Multi_dimensional_flat_barcode<>; if constexpr (PersBackend::is_vine) { // slicer.set_one_filtration(one_persistence); if (I == 0 && J == 0) [[unlikely]] // this is dangerous, assumes it starts at 0 0 @@ -225,15 +210,14 @@ inline void compute_2d_rank_invariant_of_elbow( // degrees_index[degree] = true; // } // slicer.compute_persistence(degrees_index); - slicer.compute_persistence(); + slicer.initialize_persistence_computation(); } else { slicer.vineyard_update(); } - barcodes = slicer.get_barcode(); } else { - slicer.compute_persistence(ignore_inf); - barcodes = slicer.get_barcode(); + slicer.initialize_persistence_computation(ignore_inf); } + bc_type barcodes = slicer.template get_flat_barcode(); // note one_pers not necesary when vine, but does the same computation @@ -243,13 +227,13 @@ inline void compute_2d_rank_invariant_of_elbow( if (degree >= static_cast(barcodes.size())) continue; const auto &barcode = barcodes[degree]; for (const auto &bar : barcode) { - if (bar.first > Y + I) continue; + if (bar[0] > Y + I) continue; if constexpr (verbose) - std::cout << bar.first << " " << bar.second << "checkinf: " << MultiFiltration::Generator::T_inf << " ==? " - << (bar.first == MultiFiltration::Generator::T_inf) << std::endl; - auto birth = static_cast(bar.first); + std::cout << bar[0] << " " << bar[1] << "checkinf: " << MultiFiltration::T_inf << " ==? " + << (bar[0] == MultiFiltration::T_inf) << std::endl; + auto birth = static_cast(bar[0]); auto death = static_cast( - std::min(bar.second, + std::min(bar[1], static_cast(Y + I))); // I,J atteints, pas X ni Y if constexpr (false) std::cout << "Birth " << birth << " Death " << death << std::endl; for (auto intermediate_birth = birth; intermediate_birth < death; intermediate_birth++) { @@ -271,12 +255,11 @@ inline void compute_2d_rank_invariant_of_elbow( }; template , + class MultiFiltration, typename dtype, typename index_type> inline void compute_2d_rank_invariant( - truc_interface::Truc &slicer, + Gudhi::multi_persistence::Slicer &slicer, const tensor::static_tensor_view &out, // assumes its a zero tensor const std::vector &grid_shape, const std::vector °rees, @@ -290,14 +273,14 @@ inline void compute_2d_rank_invariant( std::cout << "Shape " << grid_shape[0] << " " << grid_shape[1] << " " << grid_shape[2] << " " << grid_shape[3] << " " << grid_shape[4] << std::endl; - using ThreadSafe = typename truc_interface::Truc::ThreadSafe; + using ThreadSafe = typename Gudhi::multi_persistence::Slicer::Thread_safe; ThreadSafe slicer_thread(slicer); tbb::enumerable_thread_specific thread_locals(slicer_thread); tbb::parallel_for(0, X, [&](index_type I) { tbb::parallel_for(0, Y, [&](index_type J) { if constexpr (verbose) std::cout << "Computing elbow " << I << " " << J << "..."; ThreadSafe &slicer = thread_locals.local(); - compute_2d_rank_invariant_of_elbow( + compute_2d_rank_invariant_of_elbow( slicer, out, I, J, grid_shape, degrees, flip_death, ignore_inf); if constexpr (verbose) std::cout << "Done!" << std::endl; }); @@ -305,11 +288,10 @@ inline void compute_2d_rank_invariant( } template , + class MultiFiltration, typename dtype, typename indices_type> -void compute_rank_invariant_python(truc_interface::Truc slicer, +void compute_rank_invariant_python(Gudhi::multi_persistence::Slicer& slicer, dtype *data_ptr, const std::vector grid_shape, const std::vector degrees, @@ -328,12 +310,11 @@ void compute_rank_invariant_python(truc_interface::Truc std::pair>, std::vector> compute_rank_signed_measure( - truc_interface::Truc slicer, + Gudhi::multi_persistence::Slicer& slicer, dtype *data_ptr, const std::vector grid_shape, const std::vector degrees, @@ -352,9 +333,9 @@ std::pair>, std::vector> compute_ra } // for (indices_type axis : - // std::views::iota(2,st_multi.get_number_of_parameters()+1)) // +1 for the + // std::views::iota(2,st_multi.num_parameters()+1)) // +1 for the // degree in axis 0 - for (std::size_t axis = 0u; axis < slicer.num_parameters() + 1; axis++) container.differentiate(axis); + for (std::size_t axis = 0u; axis < slicer.get_number_of_parameters() + 1; axis++) container.differentiate(axis); if (verbose) { std::cout << "Done.\n"; std::cout << "Sparsifying the measure ..." << std::flush; diff --git a/multipers/multiparameter_module_approximation.pyx b/multipers/multiparameter_module_approximation.pyx index 7754548e..8865a96e 100644 --- a/multipers/multiparameter_module_approximation.pyx +++ b/multipers/multiparameter_module_approximation.pyx @@ -76,13 +76,13 @@ def module_approximation_from_slicer( approx_mod = PyModule_f32() if box is None: box = slicer.filtration_bounds() - mod_f32 = _multiparameter_module_approximation_f32(slicer,_py21c_f32(direction_), max_error,Box[float](box),threshold, complete, verbose) + mod_f32 = _multiparameter_module_approximation_f32(slicer,_py2p_f32(direction_), max_error,Box[float](box),threshold, complete, verbose) ptr = (&mod_f32) elif slicer.dtype == np.float64: approx_mod = PyModule_f64() if box is None: box = slicer.filtration_bounds() - mod_f64 = _multiparameter_module_approximation_f64(slicer,_py21c_f64(direction_), max_error,Box[double](box),threshold, complete, verbose) + mod_f64 = _multiparameter_module_approximation_f64(slicer,_py2p_f64(direction_), max_error,Box[double](box),threshold, complete, verbose) ptr = (&mod_f64) else: raise ValueError(f"Slicer must be float-like. Got {slicer.dtype}.") @@ -174,6 +174,8 @@ def module_approximation( mod.merge(m, input[i].minpres_degree) return mod if len(input) == 0: + if verbose: + print("Empty input, returning the trivial module.") return PyModule_f64() if input.is_squeezed: if not ignore_warnings: @@ -181,15 +183,18 @@ def module_approximation( input = input.unsqueeze() if box is None: - if is_simplextree_multi(input): - box = input.filtration_bounds() - else: - box = input.filtration_bounds() + if verbose: + print("No box given. Using filtration bounds to infer it.") + box = input.filtration_bounds() + if verbose: + print(f"Using {box=}.",flush=True) box = np.asarray(box) # empty coords zero_idx = box[1] == box[0] if np.any(zero_idx): + if not ignore_warnings: + warn(f"Got {(box[1] == box[0])=} trivial box coordinates.") box[1] += zero_idx for i in swap_box_coords: @@ -232,4 +237,3 @@ Returning the trivial module. - diff --git a/multipers/multiparameter_module_approximation/approximation.h b/multipers/multiparameter_module_approximation/approximation.h index 9cf06d2a..a69e3544 100644 --- a/multipers/multiparameter_module_approximation/approximation.h +++ b/multipers/multiparameter_module_approximation/approximation.h @@ -1,15 +1,15 @@ -/* This file is part of the MMA Library - - * https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. See - * file LICENSE for full license details. Author(s): David Loiseaux +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): David Loiseaux * * Copyright (C) 2021 Inria * * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence - * class, renaming and cleanup. + * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. * - 2022/05 Hannah Schreiber: Addition of Summand class and Module class. */ + /** * @file approximation.h * @author David Loiseaux, Hannah Schreiber @@ -27,21 +27,21 @@ #include #include #include +#include #include #include #include #include - +#include + +#include "../gudhi/Persistence_slices_interface.h" +#include "../gudhi/gudhi/Dynamic_multi_parameter_filtration.h" +#include "../gudhi/gudhi/Multi_persistence/Line.h" +#include "../gudhi/gudhi/Multi_persistence/Box.h" +#include "../tensor/tensor.h" +#include "gudhi/Multi_filtration/multi_filtration_utils.h" #include "utilities.h" - #include "debug.h" -#include -#include -#include -#include -#include -#include -#include namespace Gudhi { namespace multiparameter { @@ -60,7 +60,7 @@ void threshold_filters_list(std::vector &filtersList, const Box class Module { public: - using filtration_type = Gudhi::multi_filtration::One_critical_filtration; + using filtration_type = typename Summand::filtration_type; using module_type = std::vector>; using image_type = std::vector>; using get_2dpixel_value_function_type = std::function &box); - void resize(unsigned int size); + void resize(unsigned int size, int number_of_parameters); Summand &at(unsigned int index); Summand &operator[](size_t index); const Summand &operator[](const size_t index) const; - template - void add_barcode(const Barcode &barcode); void add_barcode(const Line &line, - const std::vector>> &barcode, - const bool threshold); - void add_barcode(const Line &line, - const std::vector> &barcode, - const bool threshold); + const std::vector>> &barcode, + bool threshold); typename module_type::iterator begin(); typename module_type::iterator end(); typename module_type::const_iterator begin() const; @@ -116,6 +111,29 @@ class Module { unsigned int verticalResolution, get_2dpixel_value_function_type get_pixel_value) const; std::vector get_landscape_values(const std::vector &x, const dimension_type dimension) const; + + template + friend Module direct_sum(const Module &a, const Module &b); + + void add_modules_from_ptr(const std::vector &modules) { + size_t total_size = this->size(); + for (const auto &m : modules) { + total_size += reinterpret_cast *>(m)->size(); + } + this->module_.resize(total_size); + size_t index = this->size(); + for (const auto &m : modules) { + std::copy(reinterpret_cast *>(m)->module_.begin(), + reinterpret_cast *>(m)->module_.end(), + this->module_.begin() + index); + index += reinterpret_cast *>(m)->size(); + } + for (const auto &m_ptr : modules) { + Module *m = reinterpret_cast *>(m_ptr); + this->module_.insert(this->module_.end(), m->module_.begin(), m->module_.end()); + } + }; + image_type get_landscape(const dimension_type dimension, const unsigned int k, const Box &box, @@ -133,21 +151,31 @@ class Module { module_type get_summands_of_dimension(const int dimension) const; std::vector>, std::vector>>> get_corners_of_dimension(const int dimension) const; - MultiDiagram get_barcode(const Line &l, - const dimension_type dimension = -1, - const bool threshold = false) const; + MultiDiagram, value_type> + get_barcode(const Line &l, const dimension_type dimension = -1, const bool threshold = false) const; std::vector>> get_barcode2(const Line &l, const dimension_type dimension) const; std::vector>>> get_barcodes2( const std::vector> &lines, const dimension_type dimension = -1) const; - MultiDiagrams get_barcodes(const std::vector> &lines, - const dimension_type dimension = -1, - const bool threshold = false) const; - MultiDiagrams get_barcodes(const std::vector &basepoints, - const dimension_type dimension = -1, - const bool threshold = false) const; - std::vector euler_curve(const std::vector &points) const; + MultiDiagrams, value_type> get_barcodes( + const std::vector> &lines, + const dimension_type dimension = -1, + const bool threshold = false) const; + MultiDiagrams, value_type> get_barcodes( + const std::vector::Point_t> &basepoints, + const dimension_type dimension = -1, + const bool threshold = false) const; + std::vector euler_curve(const std::vector> &points) const; + Module permute_summands(const std::vector &permutation){ + Module out; + out.set_box(this->get_box()); + out.resize(this->size(), this->box_.dimension()); + for (size_t i = 0; i < permutation.size(); ++i) { + out[i] = this->module_[permutation[i]]; + } + return out; + } inline Box get_bounds() const; inline void rescale(const std::vector &rescale_factors, int degree); @@ -199,7 +227,7 @@ class Module { if (a.get_dimension() != b.get_dimension()) return false; if (a.box_ != b.box_) return false; if (a.size() != b.size()) return false; - for (auto i : std::views::iota(0u, a.size())) { + for (auto i : std::ranges::views::iota(0u, a.size())) { if (a[i] != b[i]) return false; } return true; @@ -233,7 +261,7 @@ class Module { const value_type moduleWeight) const; void _add_bar_with_threshold(const Line &line, - const std::pair &bar, + const std::array &bar, const bool threshold_in, Summand &summand); }; @@ -241,14 +269,16 @@ class Module { template class Summand { public: - using births_type = Gudhi::multi_filtration::Multi_critical_filtration; - using deaths_type = Gudhi::multi_filtration::Multi_critical_filtration; - using filtration_type = typename births_type::Generator; // same for death + using births_type = Gudhi::multi_filtration::Dynamic_multi_parameter_filtration; + using deaths_type = Gudhi::multi_filtration::Dynamic_multi_parameter_filtration; + using filtration_type = + Gudhi::multi_filtration::Dynamic_multi_parameter_filtration; // same for death using dimension_type = int; - Summand(); + Summand(int number_of_parameters = 1); Summand(const births_type &birth_corners, const deaths_type &death_corners, dimension_type dimension); - Summand(const std::vector &birth_corners, - const std::vector &death_corners, + Summand(const std::vector &birth_corners, + const std::vector &death_corners, + int num_parameters, dimension_type dimension); value_type get_interleaving() const; @@ -261,22 +291,29 @@ class Summand { std::tuple distance_idx_to_upper(const filtration_type &x) const; std::tuple distance_idx_to_lower(const filtration_type &x) const; std::vector distance_idx_to(const filtration_type &x, bool full) const; - std::pair get_bar(const Line &line) const; + std::pair, + multipers::tmp_interface::Filtration_value> + get_bar(const Line &line) const; std::pair get_bar2(const Line &l) const; void add_bar(value_type baseBirth, value_type baseDeath, - const filtration_type &basepoint, + const typename Line::Point_t &basepoint, filtration_type &birth, filtration_type &death, const bool threshold, const Box &box); void add_bar(const filtration_type &birth, const filtration_type &death); - void add_bar(const filtration_type &basepoint, value_type birth, value_type death, const Box &); - - const std::vector &get_birth_list() const; - const std::vector &get_death_list() const; - const Gudhi::multi_filtration::Multi_critical_filtration &get_upset() const; - const Gudhi::multi_filtration::Multi_critical_filtration &get_downset() const; + void add_bar(const typename Line::Point_t &basepoint, + value_type birth, + value_type death, + const Box &); + + std::vector _get_birth_list() const; + std::vector _get_death_list() const; + std::vector> get_birth_list() const; + std::vector> get_death_list() const; + const births_type &get_upset() const; + const deaths_type &get_downset() const; void clean(); void complete_birth(const value_type precision); @@ -300,62 +337,62 @@ class Summand { bool contains(const filtration_type &x) const; - inline Box get_bounds() const { + Box get_bounds() const { if (birth_corners_.num_generators() == 0) return Box(); auto dimension = birth_corners_.num_parameters(); - filtration_type m(dimension, std::numeric_limits::infinity()); - filtration_type M(dimension, -std::numeric_limits::infinity()); - for (const auto &corner : birth_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - m[parameter] = std::min(m[parameter], corner[parameter]); + typename Box::Point_t m(dimension, std::numeric_limits::infinity()); + typename Box::Point_t M(dimension, -std::numeric_limits::infinity()); + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + m[p] = std::min(m[p], birth_corners_(g, p)); } } - for (const auto &corner : death_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - auto corner_i = corner[parameter]; - if (corner_i != std::numeric_limits::infinity()) - M[parameter] = std::max(M[parameter], corner[parameter]); + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + auto corner_i = death_corners_(g, p); + if (corner_i != filtration_type::T_inf) M[p] = std::max(M[p], corner_i); } } return Box(m, M); } - inline void rescale(const std::vector &rescale_factors) { + void rescale(const std::vector &rescale_factors) { if (birth_corners_.num_generators() == 0) return; auto dimension = birth_corners_.num_parameters(); - for (auto &corner : birth_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - corner[parameter] *= rescale_factors.at(parameter); + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + birth_corners_(g, p) *= rescale_factors[p]; } } - for (auto &corner : death_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - corner[parameter] *= rescale_factors.at(parameter); + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + death_corners_(g, p) *= rescale_factors[p]; } } } - inline void translate(const std::vector &translation) { + void translate(const std::vector &translation) { if (birth_corners_.num_generators() == 0) return; auto dimension = birth_corners_.num_parameters(); - for (auto &corner : birth_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - corner[parameter] += translation.at(parameter); + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + birth_corners_(g, p) += translation[p]; } } - for (auto &corner : death_corners_) { - for (auto parameter = 0u; parameter < dimension; parameter++) { - corner[parameter] += translation.at(parameter); + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { + for (unsigned int p = 0; p < dimension; ++p) { + death_corners_(g, p) += translation[p]; } } } - inline Summand grid_squeeze(const std::vector> &grid) const; + Summand grid_squeeze(const std::vector> &grid) const; private: - Gudhi::multi_filtration::Multi_critical_filtration - birth_corners_; // TODO : use Multi_critical_filtration - Gudhi::multi_filtration::Multi_critical_filtration death_corners_; + using Generator = typename births_type::Generator; + + births_type birth_corners_; + deaths_type death_corners_; value_type distanceTo0_; dimension_type dimension_; @@ -364,9 +401,9 @@ class Summand { void _add_death(const filtration_type &death); value_type _rectangle_volume(const filtration_type &a, const filtration_type &b) const; value_type _get_max_diagonal(const filtration_type &a, const filtration_type &b, const Box &box) const; - value_type d_inf(const filtration_type &a, const filtration_type &b) const; - void _factorize_min(filtration_type &a, const filtration_type &b); - void _factorize_max(filtration_type &a, const filtration_type &b); + value_type d_inf(const Generator &a, const Generator &b) const; + void _factorize_min(Generator &a, const Generator &b); + void _factorize_max(Generator &a, const Generator &b); static void _clean(std::vector &list, bool keep_inf = true); static inline void _clean(births_type &list, bool keep_inf = true) { list.remove_empty_generators(keep_inf); } @@ -377,7 +414,8 @@ class Summand { inline void threshold_filters_list(std::vector &filtersList, const Box &box) { return; for (unsigned int i = 0; i < filtersList.size(); i++) { - for (value_type &value : filtersList[i]) { + for (unsigned int p = 0; p < filtersList[i].num_parameters(); ++p) { + value_type &value = filtersList[i](0, p); value = std::min(std::max(value, box.get_lower_corner()[i]), box.get_upper_corner()[i]); } } @@ -387,13 +425,13 @@ template class LineIterator { public: using value_type = typename Filtration_value::value_type; - LineIterator(const Filtration_value &basepoint, - const Filtration_value &direction, + LineIterator(const typename Line::Point_t &basepoint, + const typename Line::Point_t &direction, value_type precision, int num_iterations) : precision(precision), remaining_iterations(num_iterations), current_line(std::move(basepoint), direction) {}; - inline LineIterator &operator++() { + LineIterator &operator++() { // auto &basepoint = current_line.base_point(); if (this->is_finished()) return *this; @@ -403,9 +441,9 @@ class LineIterator { return *this; } - inline const Line &operator*() const { return current_line; } + const Line &operator*() const { return current_line; } - inline LineIterator &next(std::size_t i) { + LineIterator &next(std::size_t i) { auto &basepoint = current_line.base_point(); if (this->is_finished()) return *this; // If we didn't reached the end, go to the next line @@ -414,7 +452,7 @@ class LineIterator { return *this; } - inline bool is_finished() const { return remaining_iterations <= 0; } + bool is_finished() const { return remaining_iterations <= 0; } private: const value_type precision; @@ -447,14 +485,13 @@ inline void __add_vineyard_trajectory_to_module(Module(), threshold); }; }; -template > +template > void _rec_mma(Module &module, - Filtration_value &basepoint, + typename Line::Point_t &basepoint, const std::vector &grid_size, int dim_to_iterate, Slicer &¤t_persistence, @@ -467,7 +504,7 @@ void _rec_mma(Module &module, return; } Slicer pers_copy; - Filtration_value basepoint_copy; + typename Line::Point_t basepoint_copy; for (int i = 0; i < grid_size[dim_to_iterate]; ++i) { // TODO : multithread, but needs matrix to be thread safe + put mutex on // module @@ -482,7 +519,7 @@ void _rec_mma(Module &module, template void _rec_mma2(Module &module, - Filtration_value &&basepoint, + typename Line::Point_t &&basepoint, const Filtration_value &direction, const std::vector &grid_size, const std::vector &signs, @@ -490,7 +527,7 @@ void _rec_mma2(Module &module, Slicer &¤t_persistence, const value_type precision, bool threshold) { - static_assert(std::is_same_v); + static_assert(std::is_same_v); if (dim_to_iterate <= axis) { if (signs[axis]) { @@ -523,15 +560,16 @@ void _rec_mma2(Module &module, for (int i = 0; i < grid_size[dim_to_iterate]; ++i) { // TODO : multithread, but needs matrix to be thread safe + put mutex on // module - _rec_mma2(module, - Filtration_value(basepoint), - direction, - grid_size, - signs, - dim_to_iterate - 1, - current_persistence.weak_copy(), - precision, - threshold); + _rec_mma2( + module, + typename Line::Point_t(basepoint), + direction, + grid_size, + signs, + dim_to_iterate - 1, + current_persistence.weak_copy(), + precision, + threshold); basepoint[dim_to_iterate] += signs[dim_to_iterate] ? precision : -precision; // current_persistence.push_to(Line(basepoint)); // current_persistence.vineyard_update(); @@ -539,20 +577,19 @@ void _rec_mma2(Module &module, } template -Module multiparameter_module_approximation( - Slicer &slicer, - const Gudhi::multi_filtration::One_critical_filtration &direction, - const value_type precision, - Box &box, - const bool threshold, - const bool complete, - const bool verbose) { +Module multiparameter_module_approximation(Slicer &slicer, + const typename Line::Point_t &direction, + const value_type precision, + Box &box, + const bool threshold, + const bool complete, + const bool verbose) { static_assert(std::is_same_v); // Value type can be exposed to python interface. if (verbose) std::cout << "Starting Module Approximation" << std::endl; /* using Filtration_value = Slicer::Filtration_value; */ - Gudhi::multi_filtration::One_critical_filtration basepoint = box.get_lower_corner(); + typename Box::Point_t basepoint = box.get_lower_corner(); const std::size_t num_parameters = box.dimension(); std::vector grid_size(num_parameters); std::vector signs(num_parameters); @@ -566,7 +603,7 @@ Module multiparameter_module_approximation( if (b < a) { std::swap(a, b); int local_shift; - if (!direction.num_parameters()) + if (!direction.size()) local_shift = grid_size[i]; else { local_shift = direction[i] > 0 ? static_cast(std::ceil(grid_size[i] / direction[i])) : 0; @@ -605,21 +642,18 @@ Module multiparameter_module_approximation( Timer timer("Initializing mma...\n", verbose); // fills the first barcode slicer.push_to(current_line); - slicer.compute_persistence(); - auto barcode = slicer.get_flat_barcode(); - auto num_bars = barcode.size(); - out.resize(num_bars); - /* Filtration_value birthContainer(num_parameters), */ - /* deathContainer(num_parameters); */ - for (std::size_t i = 0; i < num_bars; i++) { - const auto &[dim, bar] = barcode[i]; - /* const auto &[birth, death] = bar; */ - out[i].set_dimension(dim); - /* out[i].add_bar(birth, death, basepoint, birthContainer, deathContainer, - */ - /* threshold, box); */ + slicer.initialize_persistence_computation(); + auto barcode = slicer.template get_flat_barcode(); + auto num_bars = 0; + for (const auto &b : barcode) num_bars += b.size(); + out.resize(num_bars, num_parameters); + std::size_t i = 0; + for (unsigned int dim = 0; dim < barcode.size(); ++dim) { + for ([[maybe_unused]] const auto &bar : barcode[dim]) { + out[i].set_dimension(dim); + ++i; + } } - out.add_barcode(current_line, barcode, threshold); if (verbose) std::cout << "Instantiated " << num_bars << " summands" << std::endl; @@ -636,7 +670,9 @@ Module multiparameter_module_approximation( // } // TODO : change here if (verbose) { - std::cout << "Grid size " << Gudhi::multi_filtration::One_critical_filtration(grid_size) << " Signs "; + std::cout << "Grid size "; + for (auto v : grid_size) std::cout << v << " "; + std::cout << " Signs "; if (signs.empty()) { std::cout << "[]"; } else { @@ -661,15 +697,17 @@ Module multiparameter_module_approximation( for (std::size_t i = 1; i < num_parameters; i++) { // the loop is on the faces of the lower box // should be parallelizable, up to a mutex on out - if (direction.num_parameters() && direction[i] == 0.0) continue; // skip faces with codim d_i=0 + if (direction.size() && direction[i] == 0.0) continue; // skip faces with codim d_i=0 auto temp_grid_size = grid_size; temp_grid_size[i] = 0; - if (verbose) - std::cout << "Face " << i << "/" << num_parameters << " with grid size " - << Gudhi::multi_filtration::One_critical_filtration(temp_grid_size) << std::endl; + if (verbose) { + std::cout << "Face " << i << "/" << num_parameters << " with grid size "; + for (auto v : temp_grid_size) std::cout << v << " "; + std::cout << std::endl; + } // if (!direction.size() || direction[0] > 0) _rec_mma2<0>(out, - Gudhi::multi_filtration::One_critical_filtration(basepoint), + typename Line::Point_t(basepoint), direction, temp_grid_size, signs, @@ -679,11 +717,13 @@ Module multiparameter_module_approximation( threshold); } // last one, we can destroy basepoint & cie - if (!direction.num_parameters() || direction[0] > 0) { + if (!direction.size() || direction[0] > 0) { grid_size[0] = 0; - if (verbose) - std::cout << "Face " << num_parameters << "/" << num_parameters << " with grid size " - << Gudhi::multi_filtration::One_critical_filtration(grid_size) << std::endl; + if (verbose) { + std::cout << "Face " << num_parameters << "/" << num_parameters << " with grid size "; + for (auto v : grid_size) std::cout << v << " "; + std::cout << std::endl; + } _rec_mma2<1>(out, std::move(basepoint), direction, @@ -707,57 +747,23 @@ Module multiparameter_module_approximation( return out; }; -template -template -inline void Module::add_barcode(const Barcode &barcode) { - constexpr const bool verbose = false; - if (barcode.size() != module_.size()) { - std::cerr << "Barcode sizes doesn't match. Module is " << std::to_string(module_.size()) << " and barcode is " - << std::to_string(barcode.size()) << std::endl; - } - unsigned int count = 0; - for (const auto &bar_ : barcode) { - auto &summand = this->operator[](count++); - auto &[dim, bar] = bar_; - auto &[birth_filtration, death_filtration] = bar; - if constexpr (verbose) std::cout << "Birth " << birth_filtration << " Death " << death_filtration << std::endl; - summand.add_bar(birth_filtration, death_filtration); - } -} - -template -inline void Module::add_barcode( - const Line &line, - const std::vector>> &barcode, - const bool threshold_in) { - assert(barcode.size() == module_.size() && "Barcode sizes doesn't match."); - - auto count = 0U; - for (const auto &extBar : barcode) { - auto &[dim, bar] = extBar; - _add_bar_with_threshold(line, bar, threshold_in, this->operator[](count++)); - } -} - template inline void Module::add_barcode(const Line &line, - const std::vector> &barcode, + const std::vector>> &barcode, const bool threshold_in) { - assert(barcode.size() == module_.size() && "Barcode sizes doesn't match."); - auto count = 0U; - for (const auto &bar : barcode) { - _add_bar_with_threshold(line, bar, threshold_in, this->operator[](count++)); + for (const auto &bar_dim : barcode) { + for (const auto &bar : bar_dim) _add_bar_with_threshold(line, bar, threshold_in, this->operator[](count++)); } } template inline void Module::_add_bar_with_threshold(const Line &line, - const std::pair &bar, + const std::array &bar, const bool threshold_in, Summand &summand) { constexpr const bool verbose = false; - auto [birth_filtration, death_filtration] = bar; + auto birth_filtration = bar[0], death_filtration = bar[1]; if (birth_filtration >= death_filtration) return; @@ -766,7 +772,7 @@ inline void Module::_add_bar_with_threshold(const Line & << " direction " << line.direction() << std::endl; } - auto birth_container = line[birth_filtration]; + filtration_type birth_container = line[birth_filtration]; if constexpr (verbose) std::cout << " B: " << birth_container << " B*d: " << birth_filtration * line.direction(); if (birth_container.is_minus_inf()) { if (threshold_in) birth_container = box_.get_lower_corner(); @@ -774,13 +780,13 @@ inline void Module::_add_bar_with_threshold(const Line & bool allInf = true; for (std::size_t i = 0U; i < birth_container.num_parameters(); i++) { auto t = box_.get_lower_corner()[i]; - if (birth_container[i] < t - 1e-10) birth_container[i] = threshold_in ? t : -filtration_type::T_inf; - if (birth_container[i] != -filtration_type::T_inf) allInf = false; + if (birth_container(0, i) < t - 1e-10) birth_container(0, i) = threshold_in ? t : -filtration_type::T_inf; + if (birth_container(0, i) != -filtration_type::T_inf) allInf = false; } - if (allInf) birth_container = filtration_type::minus_inf(); + if (allInf) birth_container = filtration_type::minus_inf(birth_container.num_parameters()); } - auto death_container = line[death_filtration]; + filtration_type death_container = line[death_filtration]; if constexpr (verbose) std::cout << " D: " << death_container; if (death_container.is_plus_inf()) { if (threshold_in) death_container = box_.get_upper_corner(); @@ -788,10 +794,10 @@ inline void Module::_add_bar_with_threshold(const Line & bool allInf = true; for (std::size_t i = 0U; i < death_container.num_parameters(); i++) { auto t = box_.get_upper_corner()[i]; - if (death_container[i] > t + 1e-10) death_container[i] = threshold_in ? t : filtration_type::T_inf; - if (death_container[i] != filtration_type::T_inf) allInf = false; + if (death_container(0, i) > t + 1e-10) death_container(0, i) = threshold_in ? t : filtration_type::T_inf; + if (death_container(0, i) != filtration_type::T_inf) allInf = false; } - if (allInf) death_container = filtration_type::inf(); + if (allInf) death_container = filtration_type::inf(death_container.num_parameters()); } if constexpr (verbose) std::cout << " BT: " << birth_container << " DT: " << death_container << std::endl; @@ -805,8 +811,8 @@ template inline Module::Module(Box &box) : box_(box) {} template -inline void Module::resize(const unsigned int size) { - module_.resize(size); +inline void Module::resize(const unsigned int size, int number_of_parameters) { + module_.resize(size, Summand(number_of_parameters)); } template @@ -982,8 +988,8 @@ inline std::vector> Module::compute_pixels( typename module_type::iterator start; typename module_type::iterator end = module_.begin(); for (auto degree_idx = 0u; degree_idx < num_degrees; degree_idx++) { + auto d = degrees[degree_idx]; { // for Timer - auto d = degrees[degree_idx]; Debug::Timer timer("Computing image of dimension " + std::to_string(d) + " ...", verbose); start = end; while (start != module_.end() && start->get_dimension() != d) start++; @@ -1176,10 +1182,25 @@ inline std::vector>, std::vector::get_corners_of_dimension(const int dimension) const { std::vector>, std::vector>>> list; for (const Summand &summand : this->module_) { - if (summand.get_dimension() == dimension) - list.push_back(std::make_pair( - std::vector>(summand.get_birth_list().begin(), summand.get_birth_list().end()), - std::vector>(summand.get_death_list().begin(), summand.get_death_list().end()))); + if (summand.get_dimension() == dimension) { + // not optimal and only works for Dynamic_multi_filtration + auto birthList = summand._get_birth_list(); + auto deathList = summand._get_death_list(); + std::pair>, std::vector>> corner; + corner.first.resize(birthList.size()); + corner.second.resize(deathList.size()); + unsigned int i = 0; + for (auto &b : birthList) { + corner.first[i] = std::vector(b[0].begin(), b[0].end()); + ++i; + } + i = 0; + for (auto &d : deathList) { + corner.second[i] = std::vector(d[0].begin(), d[0].end()); + ++i; + } + list.push_back(std::move(corner)); + } } return list; } @@ -1208,12 +1229,13 @@ std::vector>> Module:: } template -MultiDiagram::filtration_type, value_type> +MultiDiagram, value_type> Module::get_barcode(const Line &l, const dimension_type dimension, const bool threshold) const { + using F = multipers::tmp_interface::Filtration_value; constexpr const bool verbose = false; if constexpr (verbose) std::cout << "Computing barcode of dimension " << dimension << " and threshold " << threshold << std::endl; - std::vector> barcode(this->size()); + std::vector> barcode(this->size()); std::pair threshold_bounds; if (threshold) threshold_bounds = l.get_bounds(this->box_); unsigned int summand_idx = 0; @@ -1226,8 +1248,8 @@ Module::get_barcode(const Line &l, const dimension_type /* break; */ auto pushed_summand = summand.get_bar(l); - filtration_type &pbirth = pushed_summand.first; - filtration_type &pdeath = pushed_summand.second; + F &pbirth = pushed_summand.first; + F &pdeath = pushed_summand.second; if constexpr (verbose) std::cout << "BAR : " << pbirth << " " << pdeath << std::endl; if (threshold) { auto min = l[threshold_bounds.first]; @@ -1235,7 +1257,7 @@ Module::get_barcode(const Line &l, const dimension_type if (!(pbirth < max) || !(pdeath > min)) { /* continue; */ // We still need summands to be aligned. The price to // pay is some memory. - pbirth = std::numeric_limits::infinity(); + pbirth = std::numeric_limits::infinity(); pdeath = pbirth; } pbirth.push_to_least_common_upper_bound(min); @@ -1244,16 +1266,16 @@ Module::get_barcode(const Line &l, const dimension_type barcode[summand_idx++] = MultiDiagram_point(summand.get_dimension(), pbirth, pdeath); } barcode.resize(summand_idx); - return MultiDiagram(barcode); + return MultiDiagram(barcode); } template -MultiDiagrams::filtration_type, value_type> Module::get_barcodes( +MultiDiagrams, value_type> Module::get_barcodes( const std::vector> &lines, const dimension_type dimension, const bool threshold) const { unsigned int nlines = lines.size(); - MultiDiagrams::filtration_type, value_type> out(nlines); + MultiDiagrams, value_type> out(nlines); tbb::parallel_for(0U, nlines, [&](unsigned int i) { const Line &l = lines[i]; out[i] = this->get_barcode(l, dimension, threshold); @@ -1280,12 +1302,12 @@ std::vector>>> Module< } template -MultiDiagrams::filtration_type, value_type> Module::get_barcodes( - const std::vector &basepoints, +MultiDiagrams, value_type> Module::get_barcodes( + const std::vector::Point_t> &basepoints, const dimension_type dimension, const bool threshold) const { unsigned int nlines = basepoints.size(); - MultiDiagrams::filtration_type, value_type> out(nlines); + MultiDiagrams, value_type> out(nlines); // for (unsigned int i = 0; i < nlines; i++){ tbb::parallel_for(0U, nlines, [&](unsigned int i) { const Line &l = Line(basepoints[i]); @@ -1295,13 +1317,14 @@ MultiDiagrams::filtration_type, value_type> Module -std::vector Module::euler_curve(const std::vector &points) const { +std::vector Module::euler_curve( + const std::vector> &points) const { unsigned int npts = points.size(); std::vector out(npts); // #pragma omp parallel for tbb::parallel_for(0U, static_cast(out.size()), [&](unsigned int i) { auto &euler_char = out[i]; - const filtration_type &point = points[i]; + const auto &point = points[i]; /* #pragma omp parallel for reduction(+ : euler_char) */ for (const Summand &I : this->module_) { if (I.contains(point)) { @@ -1315,9 +1338,9 @@ std::vector Module::euler_curve(const std::vector inline Box Module::get_bounds() const { - dimension_type num_parameters = box_.get_lower_corner().num_parameters(); - filtration_type lower_bound(num_parameters, std::numeric_limits::infinity()); - filtration_type upper_bound(num_parameters, -std::numeric_limits::infinity()); + dimension_type num_parameters = box_.get_lower_corner().size(); + typename Box::Point_t lower_bound(num_parameters, std::numeric_limits::infinity()); + typename Box::Point_t upper_bound(num_parameters, -std::numeric_limits::infinity()); for (const auto &summand : module_) { const auto &summand_bounds = summand.get_bounds(); const auto &[m, M] = summand_bounds.get_bounding_corners(); @@ -1435,18 +1458,18 @@ inline typename Module::idx_dump_type Module::to_idx( std::pair>, std::vector>> interval_idx; auto &birth_idx = interval_idx.first; - birth_idx.reserve(interval.get_birth_list().size()); + birth_idx.reserve(interval._get_birth_list().size()); auto &death_idx = interval_idx.second; - death_idx.reserve(interval.get_death_list().size()); + death_idx.reserve(interval._get_death_list().size()); - for (const auto &pt : interval.get_birth_list()) { + for (const auto &pt : interval._get_birth_list()) { std::vector pt_idx(pt.size()); for (auto i = 0u; i < num_parameters; ++i) { pt_idx[i] = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt[i])); } birth_idx.push_back(pt_idx); } - for (const auto &pt : interval.get_death_list()) { + for (const auto &pt : interval._get_death_list()) { std::vector pt_idx(pt.size()); for (auto i = 0u; i < num_parameters; ++i) { pt_idx[i] = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt[i])); @@ -1459,7 +1482,7 @@ inline typename Module::idx_dump_type Module::to_idx( } template -std::vector inline to_grid_coord(const Gudhi::multi_filtration::One_critical_filtration &pt, +std::vector inline to_grid_coord(const typename Summand::filtration_type &pt, const std::vector> &grid) { std::size_t num_parameters = grid.size(); std::vector out(num_parameters); @@ -1473,13 +1496,13 @@ std::vector inline to_grid_coord(const Gudhi::multi_filtration::One_critica } // pt has to be of size num_parameters now for (size_t i = 0u; i < num_parameters; ++i) { - if (pt[i] >= grid[i].back()) [[unlikely]] + if (pt(0, i) >= grid[i].back()) [[unlikely]] out[i] = grid[i].size() - 1; - else if (pt[i] <= grid[i][0]) [[unlikely]] { + else if (pt(0, i) <= grid[i][0]) [[unlikely]] { out[i] = 0; } else { - auto temp = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt[i])); - if (std::abs(grid[i][temp] - pt[i]) < std::abs(grid[i][temp - 1] - pt[i])) { + auto temp = std::distance(grid[i].begin(), std::lower_bound(grid[i].begin(), grid[i].end(), pt(0, i))); + if (std::abs(grid[i][temp] - pt(0, i)) < std::abs(grid[i][temp - 1] - pt(0, i))) { out[i] = temp; } else { out[i] = temp - 1; @@ -1506,12 +1529,12 @@ std::vector>> inline Module::to_flat_id deaths.reserve(2 * this->size()); for (auto i = 0u; i < this->size(); ++i) { auto &interval = this->operator[](i); - idx[0][i] = interval.get_birth_list().size(); - for (const auto &pt : interval.get_birth_list()) { + idx[0][i] = interval._get_birth_list().size(); + for (const auto &pt : interval._get_birth_list()) { births.push_back(to_grid_coord(pt, grid)); } - idx[1][i] = interval.get_death_list().size(); - for (const auto &pt : interval.get_death_list()) { + idx[1][i] = interval._get_death_list().size(); + for (const auto &pt : interval._get_death_list()) { deaths.push_back(to_grid_coord(pt, grid)); } } @@ -1676,24 +1699,28 @@ inline value_type Module::_get_pixel_value(const typename module_typ ///////////////////////////////////////////////// template -inline Summand::Summand() - : birth_corners_(1, births_type::Generator::T_inf), - death_corners_(1, -births_type::Generator::T_inf), +inline Summand::Summand(int number_of_parameters) + : birth_corners_(number_of_parameters, births_type::T_inf), + death_corners_(number_of_parameters, -births_type::T_inf), distanceTo0_(-1), dimension_(-1) {} template -inline Summand::Summand( - const typename std::vector::filtration_type> &birth_corners, - const typename std::vector::filtration_type> &death_corners, - dimension_type dimension) - : birth_corners_(birth_corners), death_corners_(death_corners), distanceTo0_(-1), dimension_(dimension) {} +inline Summand::Summand(const std::vector &birth_corners, + const std::vector &death_corners, + int num_parameters, + dimension_type dimension) + : birth_corners_(birth_corners.begin(), birth_corners.end(), num_parameters), + death_corners_(death_corners.begin(), death_corners.end(), num_parameters), + distanceTo0_(-1), + dimension_(dimension) {} template inline bool Summand::contains(const filtration_type &x) const { bool out = false; + // only works with Dynamic_multi_filtration_value for (const auto &birth : this->birth_corners_) { // checks if there exists a birth smaller than x - if (birth <= x) { + if (birth <= x[0]) { out = true; break; } @@ -1701,7 +1728,7 @@ inline bool Summand::contains(const filtration_type &x) const { if (!out) return false; out = false; for (const auto &death : this->death_corners_) { - if (x <= death) { + if (x[0] <= death) { out = true; break; } @@ -1710,8 +1737,8 @@ inline bool Summand::contains(const filtration_type &x) const { } template -inline Summand::Summand(const typename Summand::births_type &birth_corners, - const typename Summand::deaths_type &death_corners, +inline Summand::Summand(const births_type &birth_corners, + const deaths_type &death_corners, dimension_type dimension) : birth_corners_(birth_corners), death_corners_(death_corners), distanceTo0_(-1), dimension_(dimension) {} @@ -1730,33 +1757,40 @@ template inline value_type Summand::get_local_weight(const filtration_type &x, const value_type delta) const { bool rectangle = delta <= 0; - // TODO: add assert to verify that x.size == birth.size/death.size - // if they are not infinite. + assert(x.num_parameters() == birth_corners_.num_parameters()); filtration_type mini(x.num_parameters()); filtration_type maxi(x.num_parameters()); + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) { + mini.force_generator_size_to_number_of_parameters(0); + maxi.force_generator_size_to_number_of_parameters(0); + } // box on which to compute the local weight - for (unsigned int i = 0; i < x.size(); i++) { - mini[i] = delta <= 0 ? x[i] + delta : x[i] - delta; - maxi[i] = delta <= 0 ? x[i] - delta : x[i] + delta; + for (unsigned int i = 0; i < x.num_parameters(); i++) { + mini(0, i) = delta <= 0 ? x(0, i) + delta : x(0, i) - delta; + maxi(0, i) = delta <= 0 ? x(0, i) - delta : x(0, i) + delta; } // Pre-allocating std::vector birthList(birth_corners_.num_generators()); std::vector deathList(death_corners_.num_generators()); + // Only works with Dynamic_multi_filtration + filtration_type birth(birth_corners_.num_parameters()); + filtration_type death(birth_corners_.num_parameters()); unsigned int lastEntry = 0; - for (const filtration_type &birth : birth_corners_) { + for (const auto &b : birth_corners_) { + birth[0] = b; if (birth <= maxi) { - unsigned int dim = std::max(birth.num_parameters(), mini.num_parameters()); + unsigned int dim = birth.num_parameters(); filtration_type tmpBirth(dim); + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + tmpBirth.force_generator_size_to_number_of_parameters(0); for (unsigned int i = 0; i < dim; i++) { - auto birthi = birth.num_parameters() > i ? birth[i] : birth[0]; - auto minii = mini.num_parameters() > i ? mini[i] : mini[0]; - tmpBirth[i] = std::max(birthi, minii); + tmpBirth(0, i) = std::max(birth(0, i), mini(0, i)); } - birthList[lastEntry].swap(tmpBirth); + swap(birthList[lastEntry], tmpBirth); lastEntry++; } } @@ -1764,21 +1798,23 @@ inline value_type Summand::get_local_weight(const filtration_type &x // Thresholds birthlist & deathlist to B_inf(x,delta) lastEntry = 0; - for (const filtration_type &death : death_corners_) { + for (const auto &d : death_corners_) { + death[0] = d; if (death >= mini) { - unsigned int dim = std::max(death.num_parameters(), maxi.num_parameters()); + unsigned int dim = death.num_parameters(); filtration_type tmpDeath(dim); + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + tmpDeath.force_generator_size_to_number_of_parameters(0); for (unsigned int i = 0; i < dim; i++) { - auto deathi = death.num_parameters() > i ? death[i] : death[0]; - auto maxii = maxi.num_parameters() > i ? maxi[i] : maxi[0]; - tmpDeath[i] = std::min(deathi, maxii); + tmpDeath(0, i) = std::min(death(0, i), maxi(0, i)); } - deathList[lastEntry].swap(tmpDeath); + swap(deathList[lastEntry], tmpDeath); lastEntry++; } } deathList.resize(lastEntry); + value_type local_weight = 0; if (!rectangle) { // Local weight is inteleaving to 0 of module restricted to the square @@ -1815,11 +1851,12 @@ inline std::tuple Summand::distance_idx_to_lower(const fil int b_idx = -1; // argmin_b max_i (b-x)_x int param = 0; auto count = 0u; + // Only works with Dynamic_multi_parameter_filtration for (const auto &birth : birth_corners_) { value_type temp = -std::numeric_limits::infinity(); // max_i(birth - x)_+ int temp_idx = 0; for (auto i = 0u; i < birth.size(); ++i) { - auto plus = birth[i] - x[i]; + value_type plus = birth[i] - x(0, i); if (plus > temp) { temp_idx = i; temp = plus; @@ -1841,11 +1878,12 @@ inline std::tuple Summand::distance_idx_to_upper(const fil int d_idx = -1; // argmin_d max_i (x-death) int param = 0; auto count = 0u; + // Only works with Dynamic_multi_parameter_filtration for (const auto &death : death_corners_) { value_type temp = -std::numeric_limits::infinity(); // max_i(death-x)_+ int temp_idx = 0; for (auto i = 0u; i < death.size(); ++i) { - auto plus = x[i] - death[i]; + value_type plus = x(0, i) - death[i]; if (plus > temp) { temp_idx = i; temp = plus; @@ -1875,10 +1913,10 @@ inline std::vector Summand::distance_idx_to(const filtration_ty template inline value_type Summand::distance_to_lower(const filtration_type &x, bool negative) const { value_type distance_to_lower = std::numeric_limits::infinity(); - for (const auto &birth : birth_corners_) { + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { value_type temp = negative ? -std::numeric_limits::infinity() : 0; - for (auto i = 0u; i < birth.size(); ++i) { - temp = std::max(temp, birth[i] - x[i]); + for (unsigned int p = 0; p < birth_corners_.num_parameters(); ++p) { + temp = std::max(temp, birth_corners_(g, p) - x(0, p)); } distance_to_lower = std::min(distance_to_lower, temp); } @@ -1888,10 +1926,10 @@ inline value_type Summand::distance_to_lower(const filtration_type & template inline value_type Summand::distance_to_upper(const filtration_type &x, bool negative) const { value_type distance_to_upper = std::numeric_limits::infinity(); - for (const auto &death : death_corners_) { + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { value_type temp = negative ? -std::numeric_limits::infinity() : 0; - for (auto i = 0u; i < death.size(); ++i) { - temp = std::max(temp, x[i] - death[i]); + for (unsigned int p = 0; p < death_corners_.num_parameters(); ++p) { + temp = std::max(temp, x(0, p) - death_corners_(g, p)); } distance_to_upper = std::min(distance_to_upper, temp); } @@ -1910,12 +1948,12 @@ inline std::pair Summand::get_bar2(const Lin std::cout << "Computing bar of this summand of dimension " << this->get_dimension() << std::endl; value_type pushed_birth = std::numeric_limits::infinity(); value_type pushed_death = -pushed_birth; - for (filtration_type birth : this->get_birth_list()) { + for (filtration_type birth : this->_get_birth_list()) { value_type pb = l.compute_forward_intersection(birth); pushed_birth = std::min(pb, pushed_birth); } // - for (const filtration_type &death : this->get_death_list()) { + for (const filtration_type &death : this->_get_death_list()) { value_type pd = l.compute_backward_intersection(death); pushed_death = std::max(pd, pushed_death); } @@ -1931,32 +1969,35 @@ inline std::pair Summand::get_bar2(const Lin } template -inline std::pair::filtration_type, typename Summand::filtration_type> +inline std::pair, + multipers::tmp_interface::Filtration_value> Summand::get_bar(const Line &l) const { + using F = multipers::tmp_interface::Filtration_value; constexpr const bool verbose = false; + int num_param = birth_corners_.num_parameters(); if constexpr (verbose) std::cout << "Computing bar of this summand of dimension " << this->get_dimension() << std::endl; - filtration_type pushed_birth = std::numeric_limits::infinity(); - filtration_type pushed_death = std::numeric_limits::minus_infinity(); - for (filtration_type birth : this->get_birth_list()) { - filtration_type pb = l[l.compute_forward_intersection(birth)]; + F pushed_birth = F::inf(num_param); + F pushed_death = F::minus_inf(num_param); + for (const F &birth : this->get_birth_list()) { + F pb = l[l.compute_forward_intersection(birth)]; if constexpr (verbose) std::cout << "Updating birth " << pushed_birth << " with " << pb << " pushed at " << birth << " " << pushed_birth.is_plus_inf(); if ((pb <= pushed_birth) || pushed_birth.is_plus_inf()) { - pushed_birth.swap(pb); + swap(pushed_birth, pb); if constexpr (verbose) std::cout << " swapped !"; } if constexpr (verbose) std::cout << std::endl; } // - for (const filtration_type &death : this->get_death_list()) { - filtration_type pd = l[l.compute_backward_intersection(death)]; + for (const F &death : this->get_death_list()) { + F pd = l[l.compute_backward_intersection(death)]; if constexpr (verbose) std::cout << "Updating death " << pushed_death << " with " << pd << " pushed at " << death << " " << pushed_death.is_minus_inf() << pushed_death[0]; if ((pd >= pushed_death) || pushed_death.is_minus_inf()) { - pushed_death.swap(pd); + swap(pushed_death, pd); if constexpr (verbose) std::cout << " swapped !"; } if constexpr (verbose) std::cout << std::endl; @@ -1964,7 +2005,7 @@ Summand::get_bar(const Line &l) const { if (!(pushed_birth <= pushed_death)) { if constexpr (verbose) std::cout << "Birth ::infinity(), std::numeric_limits::infinity()}; + return {F::inf(num_param), F::inf(num_param)}; } if constexpr (verbose) { std::cout << "Final values" << pushed_birth << " ----- " << pushed_death << std::endl; @@ -1982,8 +2023,7 @@ Summand::get_bar(const Line &l) const { * @param basepoint p_basepoint: basepoint of the line of the bar * @param birth p_birth: birth container (for memory optimization purposes). * Has to be of the size @p basepoint.size()+1. - * @param death p_death: death container. Same purpose as @p birth but for - * deathpoint. + * @param death p_death: death container. Same purpose as @p birth but for death. * @param threshold p_threshold: If true, will threshold the bar with @p box. * @param box p_box: Only useful if @p threshold is set to true. */ @@ -1991,7 +2031,7 @@ Summand::get_bar(const Line &l) const { template inline void Summand::add_bar(value_type baseBirth, value_type baseDeath, - const filtration_type &basepoint, + const typename Line::Point_t &basepoint, filtration_type &birth, filtration_type &death, const bool threshold, @@ -2008,16 +2048,16 @@ inline void Summand::add_bar(value_type baseBirth, // death.back() = baseDeath; /* #pragma omp simd */ - for (unsigned int j = 0; j < birth.size() - 1; j++) { + for (unsigned int j = 0; j < birth.num_parameters() - 1; j++) { value_type temp = basepoint[j] + baseBirth; // The box is assumed to contain all of the filtration values, if its // outside, its inf. - birth[j] = temp < box.get_lower_corner()[j] ? negInf : temp; + birth(0, j) = temp < box.get_lower_corner()[j] ? negInf : temp; temp = basepoint[j] + baseDeath; - death[j] = temp > box.get_upper_corner()[j] ? inf : temp; + death(0, j) = temp > box.get_upper_corner()[j] ? inf : temp; } - birth.back() = baseBirth < box.get_lower_corner().back() ? negInf : baseBirth; - death.back() = baseDeath > box.get_upper_corner().back() ? inf : baseDeath; + birth(0, birth.num_parameters() - 1) = baseBirth < box.get_lower_corner().back() ? negInf : baseBirth; + death(0, birth.num_parameters() - 1) = baseDeath > box.get_upper_corner().back() ? inf : baseDeath; if (threshold) { // std::cout << box; @@ -2035,7 +2075,7 @@ inline void Summand::add_bar(const filtration_type &birth, const fil } template -inline void Summand::add_bar(const filtration_type &basepoint, +inline void Summand::add_bar(const typename Line::Point_t &basepoint, value_type birth, value_type death, const Box &box) { @@ -2058,22 +2098,76 @@ inline void Summand::add_bar(const filtration_type &basepoint, } template -inline const std::vector::filtration_type> &Summand::get_birth_list() const { - return birth_corners_.get_underlying_container(); +inline std::vector::filtration_type> Summand::_get_birth_list() const { + std::vector res(birth_corners_.num_generators(), filtration_type(birth_corners_.num_parameters())); + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { + // could be done in a more generic way, but this should be the fastest without changing the current interface + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + res[g].force_generator_size_to_number_of_parameters(0); + for (unsigned int p = 0; p < birth_corners_.num_parameters(); ++p) { + res[g](0, p) = birth_corners_(g, p); + } + } + + return res; +} + +template +inline std::vector::filtration_type> Summand::_get_death_list() const { + // TODO: factorize with _get_birth_list + std::vector res(death_corners_.num_generators(), filtration_type(death_corners_.num_parameters())); + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { + // could be done in a more generic way, but this should be the fastest without changing the current interface + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + res[g].force_generator_size_to_number_of_parameters(0); + for (unsigned int p = 0; p < death_corners_.num_parameters(); ++p) { + res[g](0, p) = death_corners_(g, p); + } + } + + return res; +} + +template +inline std::vector> Summand::get_birth_list() const { + using F = multipers::tmp_interface::Filtration_value; + std::vector res(birth_corners_.num_generators(), F(birth_corners_.num_parameters())); + for (unsigned int g = 0; g < birth_corners_.num_generators(); ++g) { + // could be done in a more generic way, but this should be the fastest without changing the current interface + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + res[g].force_generator_size_to_number_of_parameters(0); + for (unsigned int p = 0; p < birth_corners_.num_parameters(); ++p) { + res[g](0, p) = birth_corners_(g, p); + } + } + + return res; } template -inline const std::vector::filtration_type> &Summand::get_death_list() const { - return death_corners_.get_underlying_container(); +inline std::vector> Summand::get_death_list() const { + // TODO: factorize with _get_birth_list + using F = multipers::tmp_interface::Filtration_value; + std::vector res(death_corners_.num_generators(), F(death_corners_.num_parameters())); + for (unsigned int g = 0; g < death_corners_.num_generators(); ++g) { + // could be done in a more generic way, but this should be the fastest without changing the current interface + if constexpr (Gudhi::multi_filtration::RangeTraits::is_dynamic_multi_filtration) + res[g].force_generator_size_to_number_of_parameters(0); + for (unsigned int p = 0; p < death_corners_.num_parameters(); ++p) { + res[g](0, p) = death_corners_(g, p); + } + } + + return res; } template -const Gudhi::multi_filtration::Multi_critical_filtration &Summand::get_upset() const { +const Summand::births_type &Summand::get_upset() const { return birth_corners_; } template -const Gudhi::multi_filtration::Multi_critical_filtration &Summand::get_downset() const { +const Summand::deaths_type &Summand::get_downset() const { return death_corners_; }; @@ -2138,8 +2232,13 @@ template inline value_type Summand::get_landscape_value(const std::vector &x) const { value_type out = 0; Box trivial_box; - for (const filtration_type &b : this->birth_corners_) { - for (const filtration_type &d : this->death_corners_) { + // Only works with Dynamic_multi_parameter_filtration and is not optimal + filtration_type b(birth_corners_.num_parameters()); + filtration_type d(death_corners_.num_parameters()); + for (const auto &birth : this->birth_corners_) { + for (const auto &death : this->death_corners_) { + b[0] = birth; + d[0] = death; value_type value = std::min(this->_get_max_diagonal(b, x, trivial_box), this->_get_max_diagonal(x, d, trivial_box)); out = std::max(out, value); @@ -2157,9 +2256,14 @@ template inline void Summand::_compute_interleaving(const Box &box) { distanceTo0_ = 0; /* #pragma omp parallel for reduction(max : distanceTo0_) */ - for (const std::vector &birth : birth_corners_) { - for (const std::vector &death : death_corners_) { - distanceTo0_ = std::max(distanceTo0_, _get_max_diagonal(birth, death, box)); + // Only works with Dynamic_multi_parameter_filtration and is not optimal + filtration_type b(birth_corners_.num_parameters()); + filtration_type d(death_corners_.num_parameters()); + for (const auto &birth : birth_corners_) { + for (const auto &death : death_corners_) { + b[0] = birth; + d[0] = death; + distanceTo0_ = std::max(distanceTo0_, _get_max_diagonal(b, d, box)); } } } @@ -2176,7 +2280,7 @@ inline void Summand::_compute_interleaving(const Box &bo template inline void Summand::_add_birth(const filtration_type &birth) { - birth_corners_.add_generator(birth); + birth_corners_.add_generator(birth[0]); return; // // TODO : DEPRECATE THIS OLD CODE @@ -2213,7 +2317,7 @@ inline void Summand::_add_birth(const filtration_type &birth) { template inline void Summand::_add_death(const filtration_type &death) { - death_corners_.add_generator(death); + death_corners_.add_generator(death[0]); return; // // TODO: Deprecate this old code // if (death_corners_.empty()) { @@ -2241,27 +2345,22 @@ inline value_type Summand::_get_max_diagonal(const filtration_type & const filtration_type &death, const Box &box) const { // assumes birth and death to be never NaN - if constexpr (Debug::debug) - assert(!birth.is_finite || !death.is_finite || birth.size() == death.size() && "Inputs must be of the same size !"); + assert(birth.num_parameters() == death.num_parameters() && "Inputs must be of the same size !"); + assert((box.is_trivial() || birth.num_parameters() == box.dimension()) && "Inputs must be of the same size !"); value_type s = inf; bool threshold_flag = !box.is_trivial(); if (threshold_flag) { - unsigned int dim = std::max(birth.size(), box.dimension()); - for (unsigned int i = 0; i < dim; ++i) { - value_type max_i = box.get_upper_corner().size() > i ? box.get_upper_corner()[i] : inf; - value_type min_i = box.get_lower_corner().size() > i ? box.get_lower_corner()[i] : negInf; - value_type t_death = death.is_plus_inf() ? max_i : (death.is_minus_inf() ? -inf : std::min(death[i], max_i)); - value_type t_birth = birth.is_plus_inf() ? inf : (birth.is_minus_inf() ? min_i : std::max(birth[i], min_i)); + for (unsigned int i = 0; i < birth.num_parameters(); ++i) { + value_type max_i = box.get_upper_corner()[i]; + value_type min_i = box.get_lower_corner()[i]; + value_type t_death = death.is_plus_inf() ? max_i : (death.is_minus_inf() ? -inf : std::min(death(0, i), max_i)); + value_type t_birth = birth.is_plus_inf() ? inf : (birth.is_minus_inf() ? min_i : std::max(birth(0, i), min_i)); s = std::min(s, t_death - t_birth); } } else { - unsigned int dim = std::max(birth.size(), death.size()); - for (unsigned int i = 0; i < dim; i++) { - // if they don't have the same size, then one of them has to (+/-)infinite. - value_type t_death = death.size() > i ? death[i] : death[0]; // assumes death is never empty - value_type t_birth = birth.size() > i ? birth[i] : birth[0]; // assumes birth is never empty - s = std::min(s, t_death - t_birth); + for (unsigned int i = 0; i < birth.num_parameters(); i++) { + s = std::min(s, death(0, i) - birth(0, i)); } } @@ -2270,17 +2369,17 @@ inline value_type Summand::_get_max_diagonal(const filtration_type & template inline value_type Summand::_rectangle_volume(const filtration_type &a, const filtration_type &b) const { - if constexpr (Debug::debug) assert(a.size() == b.size() && "Inputs must be of the same size !"); - value_type s = b[0] - a[0]; - for (unsigned int i = 1; i < a.size(); i++) { - s = s * (b[i] - a[i]); + if constexpr (Debug::debug) assert(a.num_parameters() == b.num_parameters() && "Inputs must be of the same size !"); + value_type s = b(0, 0) - a(0, 0); + for (unsigned int i = 1; i < a.num_parameters(); i++) { + s = s * (b(0, i) - a(0, i)); } return s; } template -inline value_type Summand::d_inf(const filtration_type &a, const filtration_type &b) const { - if (a.empty() || b.empty() || a.size() != b.size()) return inf; +inline value_type Summand::d_inf(const Generator &a, const Generator &b) const { + if (a.size() == 0 || b.size() == 0 || a.size() != b.size()) return inf; value_type d = std::abs(a[0] - b[0]); for (unsigned int i = 1; i < a.size(); i++) d = std::max(d, std::abs(a[i] - b[i])); @@ -2289,7 +2388,7 @@ inline value_type Summand::d_inf(const filtration_type &a, const fil } template -inline void Summand::_factorize_min(filtration_type &a, const filtration_type &b) { +inline void Summand::_factorize_min(Generator &a, const Generator &b) { /* if (Debug::debug && (a.empty() || b.empty())) */ /* { */ /* std::cout << "Empty corners ??\n"; */ @@ -2300,7 +2399,7 @@ inline void Summand::_factorize_min(filtration_type &a, const filtra } template -inline void Summand::_factorize_max(filtration_type &a, const filtration_type &b) { +inline void Summand::_factorize_max(Generator &a, const Generator &b) { /* if (Debug::debug && (a.empty() || b.empty())) */ /* { */ /* std::cout << "Empty corners ??\n"; */ @@ -2323,12 +2422,37 @@ template inline void Summand::_clean(std::vector &list, bool keep_inf) { list.erase(std::remove_if(list.begin(), list.end(), - [keep_inf](filtration_type &a) { - return a.empty() || ((!keep_inf) && (a.is_plus_inf() || a.is_minus_inf())); + [keep_inf](const filtration_type &a) { + return a.num_parameters() == 0 || ((!keep_inf) && (a.is_plus_inf() || a.is_minus_inf())); }), list.end()); } +template +Module direct_sum(const Module &a, const Module &b) { + Module out; + out.module_.resize(a.size() + b.size()); + std::copy(a.module_.begin(), a.module_.end(), out.module_.begin()); + std::copy(b.module_.begin(), b.module_.end(), out.module_.begin() + a.size()); + out.box_ = smallest_enclosing_box(a.box_, b.box_); + return out; +} + +template +Module direct_sum(std::vector *> &modules) { + Module out; + size_t total_size = 0; + for (const auto &m : modules) { + total_size += m->size(); + } + out.module_.resize(total_size); + size_t index = 0; + for (const auto &m : modules) { + std::copy(m->module_.begin(), m->module_.end(), out.module_.begin() + index); + index += m->size(); + } + return out; +} } // namespace mma } // namespace multiparameter } // namespace Gudhi diff --git a/multipers/multiparameter_module_approximation/combinatory.h b/multipers/multiparameter_module_approximation/combinatory.h deleted file mode 100644 index c8bf4d33..00000000 --- a/multipers/multiparameter_module_approximation/combinatory.h +++ /dev/null @@ -1,129 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. - */ -/** - * @file combinatory.h - * @author David Loiseaux, Hannah Schreiber - * @brief Combinatorial and sorting functions - */ - -#ifndef COMBINATORY_H_INCLUDED -#define COMBINATORY_H_INCLUDED - -#include -#include -#include -#include -#include -#include -#include "utilities.h" -#include "debug.h" - -namespace Gudhi::multiparameter::mma::Combinatorics { - -using Gudhi::multiparameter::mma::permutation_type; - -template void compose(std::vector &p, const permutation_type &q); -unsigned int prod(const std::vector& toMultiply, - unsigned int until = UINT_MAX); -template -permutation_type sort_and_return_permutation( - std::vector& toSort, - std::function lessOrEqualComparator); -template -void quicksort_and_record_permutation( - std::vector& toSort, - permutation_type& p, - unsigned int low, - unsigned int high, - std::function lessOrEqualComparator); - -template -void compose(std::vector &p,const permutation_type &q){ - unsigned int n = p.size(); -// assert(q.size() == n); - std::vector r(n); - for(unsigned int i = 0; i< n; i++){ - r[i] = p[q[i]]; - } - p.swap(r); -} - -template -std::vector inverse(const std::vector &p){ - unsigned int n = p.size(); - std::vector inv(n); - for(unsigned int i = 0; i< n; i++) - inv[p[i]] = i; - - return inv; -} - -unsigned int prod(const std::vector& toMultiply, - unsigned int until) -{ - unsigned int output = 1; - for (unsigned int i = 0; i < toMultiply.size() && i <= until; i++){ - output *= toMultiply[i]; - } - return output; -} - -template -permutation_type sort_and_return_permutation( - std::vector& toSort, std::function lessOrEqualComparator) -{ - unsigned int n = toSort.size(); - - // initialize p as the identity - permutation_type p(n); - for (unsigned int i = 0; i < n ; i++) p[i] = i; - - // call the recursive function doing the job - quicksort_and_record_permutation(toSort, p, 0, n - 1, lessOrEqualComparator); - - return p; -} - -template -void quicksort_and_record_permutation( - std::vector& toSort, - permutation_type& p, - unsigned int low, - unsigned int high, - std::function lessOrEqualComparator) -{ - // compatibility check - assert(toSort.size() == p.size()); - assert(high < toSort.size()); - - if (high <= low) return; - - // take the last element as pivot. - T pivot = toSort[high]; - - int i = low - 1 ; - - for (unsigned int j = low; j < high; j++){ - if (lessOrEqualComparator(toSort[j], pivot)){ - i++; - std::swap(toSort[i], toSort[j]); - std::swap(p[i], p[j]); - } - } - std::swap(toSort[i+1], toSort[high]); - std::swap(p[i+1], p[high]); - - quicksort_and_record_permutation(toSort, p, low, std::max(i, 0), lessOrEqualComparator); - quicksort_and_record_permutation(toSort, p, i + 2, high, lessOrEqualComparator); -} - -} //namespace Combinatorics - -#endif // COMBINATORY_H_INCLUDED diff --git a/multipers/multiparameter_module_approximation/euler_curves.h b/multipers/multiparameter_module_approximation/euler_curves.h deleted file mode 100644 index e69de29b..00000000 diff --git a/multipers/multiparameter_module_approximation/format_python-cpp.h b/multipers/multiparameter_module_approximation/format_python-cpp.h index 8362b5a0..b2f6df9e 100644 --- a/multipers/multiparameter_module_approximation/format_python-cpp.h +++ b/multipers/multiparameter_module_approximation/format_python-cpp.h @@ -21,9 +21,8 @@ #include #include -#include "multiparameter_module_approximation/utilities.h" -#include -#include +#include "../gudhi/gudhi/Simplex_tree.h" +#include "utilities.h" namespace Gudhi::multiparameter::mma { @@ -105,7 +104,7 @@ bool inline is_strictly_smaller_simplex(const boundary_type &s1, template using scc_type = - std::vector>, boundary_matrix>>; + std::vector>, boundary_matrix>>; template inline scc_type simplextree_to_scc(Gudhi::Simplex_tree &st) { scc_type out(st.dimension() + 1); @@ -126,17 +125,19 @@ inline scc_type simplextree_to_scc(Gudhi::Simplex_tree &st auto &[block_filtrations, block_matrix] = out[st.dimension(simplex)]; const typename STOptions::Filtration_value &simplex_filtration = st.filtration(simplex); block_matrix.push_back(key_boundary_container); - - block_filtrations.push_back(static_cast(simplex_filtration)); + block_filtrations.emplace_back(simplex_filtration.num_parameters()); + for (unsigned int p = 0; p < simplex_filtration.num_parameters(); ++p){ + block_filtrations.back()[p] = simplex_filtration(0, p); + } } return out; } template using kscc_type = - std::vector>>, boundary_matrix>>; + std::vector>>, boundary_matrix>>; template inline kscc_type kcritical_simplextree_to_scc(Gudhi::Simplex_tree &st) { - static_assert(STOptions::Filtration_value::is_multi_critical); + // static_assert(STOptions::Filtration_value::is_multi_critical); kscc_type out(st.dimension() + 1); if (st.num_simplices() <= 0) return out; @@ -155,9 +156,14 @@ inline kscc_type kcritical_simplextree_to_scc(Gudhi::Simplex_tree>( - simplex_filtration.begin(), simplex_filtration.end())); + block_filtrations.emplace_back( + simplex_filtration.num_generators(), + std::vector(simplex_filtration.num_parameters())); + for (unsigned int g = 0; g < simplex_filtration.num_generators(); ++g) { + for (unsigned int p = 0; p < simplex_filtration.num_parameters(); ++p) { + block_filtrations.back()[g][p] = simplex_filtration(g, p); + } + } } return out; } @@ -189,9 +195,9 @@ function_simplextree_to_scc(Gudhi::Simplex_tree &st) { const auto &simplex_filtration = st.filtration(simplex); block_matrix.push_back(key_boundary_container); std::vector> _filtration; - for (std::size_t i = 0; i < simplex_filtration.size(); i++) { + for (std::size_t i = 0; i < simplex_filtration.num_parameters(); i++) { _filtration.push_back( - {static_cast(simplex_filtration[i]), static_cast(i)}); + {static_cast(simplex_filtration(0,i)), static_cast(i)}); } block_filtrations.push_back(_filtration); } @@ -216,10 +222,10 @@ using flattened_scc_type = std::vector>>; template -flattened_scc_type inline simplextree_to_ordered_bf( +flattened_scc_type inline simplextree_to_ordered_bf( Gudhi::Simplex_tree &st) { auto scc = simplextree_to_scc(st); - flattened_scc_type out; + flattened_scc_type out; auto &[filtration, boundary] = out; std::size_t num_simplices = 0; std::vector cumsum_sizes = {0, 0}; diff --git a/multipers/multiparameter_module_approximation/heap_column.h b/multipers/multiparameter_module_approximation/heap_column.h deleted file mode 100644 index b3ccd5a9..00000000 --- a/multipers/multiparameter_module_approximation/heap_column.h +++ /dev/null @@ -1,238 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef HEAPCOLUMN_H -#define HEAPCOLUMN_H - -#include -#include -#include -#include -#include -#include - -#include "utilities.h" - -namespace Vineyard { - -class Heap_column -{ -public: - Heap_column(); - Heap_column(boundary_type& boundary); - Heap_column(Heap_column& column); - Heap_column(Heap_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(Heap_column& column); - - Heap_column& operator=(Heap_column other); - - friend void swap(Heap_column& col1, Heap_column& col2); - -private: - int dim_; - std::vector column_; - unsigned int insertsSinceLastPrune_; - std::unordered_set erasedValues_; - - void _prune(); - int _pop_pivot(); -}; - -inline Heap_column::Heap_column() : dim_(0), insertsSinceLastPrune_(0) -{} - -inline Heap_column::Heap_column(boundary_type& boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary), - insertsSinceLastPrune_(0) -{ - std::make_heap(column_.begin(), column_.end()); -} - -inline Heap_column::Heap_column(Heap_column& column) - : dim_(column.dim_), - column_(column.column_), - insertsSinceLastPrune_(column.insertsSinceLastPrune_), - erasedValues_(column.erasedValues_) -{} - -inline Heap_column::Heap_column(Heap_column&& column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)), - insertsSinceLastPrune_(std::exchange(column.insertsSinceLastPrune_, 0)), - erasedValues_(std::move(column.erasedValues_)) -{} - -inline void Heap_column::get_content(boundary_type &container) -{ - _prune(); - container = column_; - std::sort_heap(container.begin(), container.end()); -} - -inline bool Heap_column::contains(unsigned int value) const -{ - if (erasedValues_.find(value) != erasedValues_.end()) return false; - - unsigned int c = 0; - - for (unsigned int v : column_){ - if (v == value) c++; - } - - return c % 2 != 0; -} - -inline bool Heap_column::is_empty() -{ - int pivot = _pop_pivot(); - if (pivot != -1){ - column_.push_back(pivot); - std::push_heap(column_.begin(), column_.end()); - return false; - } - return true; -} - -inline dimension_type Heap_column::get_dimension() const -{ - return dim_; -} - -inline int Heap_column::get_pivot() -{ - int pivot = _pop_pivot(); - if (pivot != -1){ - column_.push_back(pivot); - std::push_heap(column_.begin(), column_.end()); - } - return pivot; -} - -inline void Heap_column::clear() -{ - column_.clear(); - insertsSinceLastPrune_ = 0; - erasedValues_.clear(); -} - -inline void Heap_column::clear(unsigned int value) -{ - erasedValues_.insert(value); -} - -inline void Heap_column::reorder(std::vector &valueMap) -{ - std::vector tempCol; - int pivot = _pop_pivot(); - while (pivot != -1) { - tempCol.push_back(valueMap.at(pivot)); - pivot = _pop_pivot(); - } - column_.swap(tempCol); - std::make_heap(column_.begin(), column_.end()); - - insertsSinceLastPrune_ = 0; - erasedValues_.clear(); -} - -inline void Heap_column::add(Heap_column &column) -{ - std::vector& colToAdd = column.column_; - const unsigned int size = colToAdd.size(); - - if (size == 0) return; - - for (unsigned int v : colToAdd) { - if (column.erasedValues_.find(v) == column.erasedValues_.end()){ - column_.push_back(v); - std::push_heap(column_.begin(), column_.end()); - erasedValues_.erase(v); - } - } - insertsSinceLastPrune_ += size; - - if (2 * insertsSinceLastPrune_ > column_.size()) _prune(); -} - -inline Heap_column& Heap_column::operator=(Heap_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - std::swap(insertsSinceLastPrune_, other.insertsSinceLastPrune_); - std::swap(erasedValues_, other.erasedValues_); - return *this; -} - -inline void Heap_column::_prune() -{ - if (insertsSinceLastPrune_ == 0 && erasedValues_.empty()) return; - - std::vector tempCol; - int pivot = _pop_pivot(); - while (pivot != -1) { - tempCol.push_back(pivot); - pivot = _pop_pivot(); - } - column_.swap(tempCol); - std::make_heap(column_.begin(), column_.end()); - - insertsSinceLastPrune_ = 0; - erasedValues_.clear(); -} - -inline int Heap_column::_pop_pivot() -{ - if (column_.empty()) { - return -1; - } - - unsigned int pivot = column_.front(); - std::pop_heap(column_.begin(), column_.end()); - column_.pop_back(); - while (!column_.empty() && column_.front() == pivot) - { - std::pop_heap(column_.begin(), column_.end()); - column_.pop_back(); - - if (column_.empty()) { - return -1; - } - pivot = column_.front(); - std::pop_heap(column_.begin(), column_.end()); - column_.pop_back(); - } - - if (erasedValues_.find(pivot) != erasedValues_.end()) - pivot = _pop_pivot(); - - return pivot; -} - -inline void swap(Heap_column& col1, Heap_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); - std::swap(col1.insertsSinceLastPrune_, col2.insertsSinceLastPrune_); - std::swap(col1.erasedValues_, col2.erasedValues_); -} - -} //namespace Vineyard - -#endif // HEAPCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/images.h b/multipers/multiparameter_module_approximation/images.h deleted file mode 100644 index 5a7e1e94..00000000 --- a/multipers/multiparameter_module_approximation/images.h +++ /dev/null @@ -1,79 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. - */ -/** - * @file images.h - * @author David Loiseaux, Hannah Schreiber - * @brief Functions to generate multipersistence images - */ - -#ifndef IMAGES_H_INCLUDED -#define IMAGES_H_INCLUDED - -#include - -#include "approximation.h" -#include "utilities.h" -namespace Gudhi::multiparameter::mma::representation{ - - -using Gudhi::multiparameter::mma::boundary_matrix; -using Gudhi::multiparameter::mma::filtration_type; -using Gudhi::multiparameter::mma::dimension_type; -using Gudhi::multiparameter::mma::Module; -using Gudhi::multiparameter::mma::Box; - -struct ImageArgs{ -private: - unsigned int module_dim; // number of persistent parameters. -public: - std::vector resolution; // resolution of the image - double p; // sum smoothing - bool normalize; // Enforce image to take values in [0,1] - int dimension; // if negative -> all dim - Box box; // Possible sub-box. -}; - - - -std::vector > > get_2D_image_from_boundary_matrix( - boundary_matrix &boundaryMatrix, - std::vector &filtersList, - const double precision, - const Box &box, - const double delta, - const double p, - const bool normalize, - const std::vector &resolution, - const dimension_type dimension, - const bool complete = true, - const bool verbose = false) -{ - Box bbox(box); - bbox.inflate(delta); - Module approximation = - Gudhi::multiparameter::mma::compute_vineyard_barcode_approximation( - boundaryMatrix, - filtersList, - precision, - bbox, - true, - complete, - false, - verbose); - - if (dimension < 0) - return approximation.get_vectorization(delta, p, normalize, resolution[0], resolution[1]); - - return {approximation.get_vectorization_in_dimension(dimension, delta, p, normalize, resolution[0], resolution[1])}; -} - -} //namespace - -#endif // IMAGES_H_INCLUDED diff --git a/multipers/multiparameter_module_approximation/list_column.h b/multipers/multiparameter_module_approximation/list_column.h deleted file mode 100644 index d6e01bb3..00000000 --- a/multipers/multiparameter_module_approximation/list_column.h +++ /dev/null @@ -1,174 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef LISTCOLUMN_H -#define LISTCOLUMN_H - -#include -#include -#include - -#include "utilities.h" - -namespace Vineyard { - -class List_column -{ -public: - List_column(); - List_column(boundary_type& boundary); - List_column(List_column& column); - List_column(List_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(List_column& column); - - List_column& operator=(List_column other); - - friend void swap(List_column& col1, List_column& col2); - -private: - int dim_; - std::list column_; -}; - -inline List_column::List_column() : dim_(0) -{} - -inline List_column::List_column(boundary_type &boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary.begin(), boundary.end()) -{} - -inline List_column::List_column(List_column &column) - : dim_(column.dim_), - column_(column.column_) -{} - -inline List_column::List_column(List_column &&column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)) -{} - -inline void List_column::get_content(boundary_type &container) -{ - std::copy(column_.begin(), column_.end(), std::back_inserter(container)); -} - -inline bool List_column::contains(unsigned int value) const -{ - for (unsigned int v : column_){ - if (v == value) return true; - } - return false; -} - -inline bool List_column::is_empty() -{ - return column_.empty(); -} - -inline dimension_type List_column::get_dimension() const -{ - return dim_; -} - -inline int List_column::get_pivot() -{ - if (column_.empty()) return -1; - - return column_.back(); -} - -inline void List_column::clear() -{ - column_.clear(); -} - -inline void List_column::clear(unsigned int value) -{ - auto it = column_.begin(); - while (it != column_.end() && *it != value) it++; - if (it != column_.end()) column_.erase(it); -} - -inline void List_column::reorder(std::vector &valueMap) -{ - std::list::iterator it = column_.begin(); - while (it != column_.end()) { - *it = valueMap.at(*it); - it++; - } - column_.sort(); -} - -inline void List_column::add(List_column &column) -{ - if (column.is_empty()) return; - if (column_.empty()){ - std::copy(column.column_.begin(), column.column_.end(), std::back_inserter(column_)); - return; - } - - std::list::iterator itToAdd = column.column_.begin(); - std::list::iterator itTarget = column_.begin(); - unsigned int valToAdd = *itToAdd; - unsigned int valTarget = *itTarget; - - while (itToAdd != column.column_.end() && itTarget != column_.end()) - { - if (itToAdd != column.column_.end() && itTarget != column_.end()){ - if (valToAdd == valTarget){ - column_.erase(itTarget++); - itToAdd++; - } else if (valToAdd < valTarget){ - column_.insert(itTarget, valToAdd); - itToAdd++; - } else { - itTarget++; - } - } - - valToAdd = *itToAdd; - valTarget = *itTarget; - } - - while (itToAdd != column.column_.end()){ - valToAdd = *itToAdd; - if (itToAdd != column.column_.end()){ - column_.push_back(valToAdd); - itToAdd++; - } - } -} - -inline List_column &List_column::operator=(List_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - return *this; -} - -inline void swap(List_column& col1, List_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); -} - -} //namespace Vineyard - -#endif // LISTCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/list_column_2.h b/multipers/multiparameter_module_approximation/list_column_2.h deleted file mode 100644 index bb1c547a..00000000 --- a/multipers/multiparameter_module_approximation/list_column_2.h +++ /dev/null @@ -1,232 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef LISTCOLUMN_H -#define LISTCOLUMN_H - -#include -#include -#include - -#include "utilities.h" - -namespace Vineyard { - -class List_column -{ -public: - List_column(); - List_column(boundary_type& boundary); - List_column(List_column& column); - List_column(List_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(List_column& column); - - List_column& operator=(List_column other); - - friend void swap(List_column& col1, List_column& col2); - -private: - int dim_; - std::list column_; - std::unordered_set erasedValues_; - - void _cleanValues(); -}; - -inline List_column::List_column() : dim_(0) -{} - -inline List_column::List_column(boundary_type &boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary.begin(), boundary.end()) -{} - -inline List_column::List_column(List_column &column) - : dim_(column.dim_), - column_(column.column_), - erasedValues_(column.erasedValues_) -{} - -inline List_column::List_column(List_column &&column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)), - erasedValues_(std::move(column.erasedValues_)) -{} - -inline void List_column::get_content(boundary_type &container) -{ - _cleanValues(); - std::copy(column_.begin(), column_.end(), std::back_inserter(container)); -} - -inline bool List_column::contains(unsigned int value) const -{ - if (erasedValues_.find(value) != erasedValues_.end()) return false; - - for (unsigned int v : column_){ - if (v == value) return true; - } - return false; -} - -inline bool List_column::is_empty() -{ - _cleanValues(); - return column_.empty(); -} - -inline dimension_type List_column::get_dimension() const -{ - return dim_; -} - -inline int List_column::get_pivot() -{ - while (!column_.empty() && - erasedValues_.find(column_.back()) != erasedValues_.end()) { - erasedValues_.erase(column_.back()); - column_.pop_back(); - } - - if (column_.empty()) return -1; - - return column_.back(); -} - -inline void List_column::clear() -{ - column_.clear(); - erasedValues_.clear(); -} - -inline void List_column::clear(unsigned int value) -{ - erasedValues_.insert(value); -} - -inline void List_column::reorder(std::vector &valueMap) -{ - std::list::iterator it = column_.begin(); - while (it != column_.end()) { - unsigned int erased = erasedValues_.erase(*it); - if (erased > 0) - it = column_.erase(it); - else { - *it = valueMap.at(*it); - it++; - } - } - column_.sort(); - erasedValues_.clear(); -} - -inline void List_column::add(List_column &column) -{ - if (column.is_empty()) return; - if (column_.empty()){ - std::copy(column.column_.begin(), column.column_.end(), std::back_inserter(column_)); - return; - } - - std::list::iterator itToAdd = column.column_.begin(); - std::list::iterator itTarget = column_.begin(); - unsigned int valToAdd = *itToAdd; - unsigned int valTarget = *itTarget; - - while (itToAdd != column.column_.end() && itTarget != column_.end()) - { - while (itToAdd != column.column_.end() && - column.erasedValues_.find(valToAdd) != column.erasedValues_.end()) { - column.column_.erase(itToAdd++); - column.erasedValues_.erase(valToAdd); - valToAdd = *itToAdd; - } - - while (itTarget != column_.end() && - erasedValues_.find(valTarget) != erasedValues_.end()) { - column_.erase(itTarget++); - valTarget = *itTarget; - } - - if (itToAdd != column.column_.end() && itTarget != column_.end()){ - if (valToAdd == valTarget){ - column_.erase(itTarget++); - itToAdd++; - } else if (valToAdd < valTarget){ - column_.insert(itTarget, valToAdd); - itToAdd++; - } else { - itTarget++; - } - } - - valToAdd = *itToAdd; - valTarget = *itTarget; - } - - while (itToAdd != column.column_.end()){ - valToAdd = *itToAdd; - - while (itToAdd != column.column_.end() && - column.erasedValues_.find(valToAdd) != column.erasedValues_.end()) { - column.column_.erase(itToAdd++); - column.erasedValues_.erase(valToAdd); - valToAdd = *itToAdd; - } - - if (itToAdd != column.column_.end()){ - column_.push_back(valToAdd); - itToAdd++; - } - } - - erasedValues_.clear(); -} - -inline List_column &List_column::operator=(List_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - std::swap(erasedValues_, other.erasedValues_); - return *this; -} - -inline void List_column::_cleanValues() -{ - std::list::iterator it = column_.begin(); - while (it != column_.end() && !erasedValues_.empty()) { - unsigned int erased = erasedValues_.erase(*it); - if (erased > 0) - it = column_.erase(it); - else - it++; - } - erasedValues_.clear(); -} - -inline void swap(List_column& col1, List_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); - std::swap(col1.erasedValues_, col2.erasedValues_); -} - -} //namespace Vineyard - -#endif // LISTCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/ru_matrix.h b/multipers/multiparameter_module_approximation/ru_matrix.h deleted file mode 100644 index e0635c74..00000000 --- a/multipers/multiparameter_module_approximation/ru_matrix.h +++ /dev/null @@ -1,347 +0,0 @@ -/* This file is part of the MMA Library - - * https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. See - * file LICENSE for full license details. Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef RU_MATRIX_H -#define RU_MATRIX_H - -#include - -#include "utilities.h" //type definitions -#include "vector_matrix.h" - -namespace Gudhi::multiparameter::mma { - -template class RU_matrix { -public: - RU_matrix(); - RU_matrix(boundary_matrix &orderedBoundaries); - RU_matrix(int numberOfColumns); - RU_matrix(RU_matrix &matrixToCopy); - RU_matrix(RU_matrix &&other) noexcept; - - void insert_boundary(index columnIndex, boundary_type &boundary); - dimension_type get_dimension(index index); - unsigned int get_number_of_simplices(); - - void initialize(); - void vine_swap(index index); - const barcode_type &get_current_barcode(); - - void print_matrices(); // for debug - - RU_matrix &operator=(RU_matrix other); - template - friend void swap(RU_matrix &matrix1, - RU_matrix &matrix2); - -private: - Vector_matrix reducedMatrixR_; - Vector_matrix mirrorMatrixU_; - barcode_type barcode_; - std::vector indexToBar_; - - void _initialize_U(); - void _swap_at_index(index index); - void _add_to(index sourceIndex, index targetIndex); - void _positive_vine_swap(index index); - void _negative_vine_swap(index index); - void _positive_negative_vine_swap(index index); - void _negative_positive_vine_swap(index index); -}; - -template inline RU_matrix::RU_matrix() {} - -template -inline RU_matrix::RU_matrix(boundary_matrix &orderedBoundaries) - : reducedMatrixR_(orderedBoundaries), - mirrorMatrixU_(orderedBoundaries.size()) { - _initialize_U(); -} - -template -inline RU_matrix::RU_matrix(int numberOfColumns) - : reducedMatrixR_(numberOfColumns), mirrorMatrixU_(numberOfColumns) { - _initialize_U(); -} - -template -inline RU_matrix::RU_matrix(RU_matrix &matrixToCopy) - : reducedMatrixR_(matrixToCopy.reducedMatrixR_), - mirrorMatrixU_(matrixToCopy.mirrorMatrixU_), - barcode_(matrixToCopy.barcode_), indexToBar_(matrixToCopy.indexToBar_) {} - -template -inline RU_matrix::RU_matrix( - RU_matrix &&other) noexcept - : reducedMatrixR_(std::move(other.reducedMatrixR_)), - mirrorMatrixU_(std::move(other.mirrorMatrixU_)), - barcode_(std::move(other.barcode_)), - indexToBar_(std::move(other.indexToBar_)) {} - -template -inline void RU_matrix::insert_boundary(index columnIndex, - boundary_type &boundary) { - reducedMatrixR_.insert_boundary(columnIndex, boundary); - boundary_type id(1, columnIndex); - mirrorMatrixU_.insert_column(columnIndex, Column_type(id)); -} - -template -inline dimension_type RU_matrix::get_dimension(index index) { - return reducedMatrixR_.get_column_dimension(index); -} - -template -inline unsigned int RU_matrix::get_number_of_simplices() { - return reducedMatrixR_.get_number_of_columns(); -} - -template inline void RU_matrix::initialize() { - std::unordered_map pivotsToColumn; - indexToBar_.resize(reducedMatrixR_.get_number_of_columns(), -1); - - for (unsigned int i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { - if (!(reducedMatrixR_.is_zero_column(i))) { - Column_type &curr = reducedMatrixR_.get_column(i); - int pivot = curr.get_pivot(); - - while (pivot != -1 && - pivotsToColumn.find(pivot) != pivotsToColumn.end()) { - curr.add(reducedMatrixR_.get_column(pivotsToColumn.at(pivot))); - mirrorMatrixU_.get_column(pivotsToColumn.at(pivot)) - .add(mirrorMatrixU_.get_column(i)); - pivot = curr.get_pivot(); - } - - if (pivot != -1) { - pivotsToColumn.emplace(pivot, i); - barcode_.at(indexToBar_.at(pivot)).death = i; - indexToBar_.at(i) = indexToBar_.at(pivot); - } else { - barcode_.push_back(Bar(get_dimension(i), i, -1)); - indexToBar_.at(i) = barcode_.size() - 1; - } - } else { - barcode_.push_back(Bar(get_dimension(i), i, -1)); - indexToBar_.at(i) = barcode_.size() - 1; - } - } -} - -template -inline void RU_matrix::vine_swap(index index) { - if (index >= reducedMatrixR_.get_number_of_columns() - 1) - return; - - bool iIsPositive = - (barcode_.at(indexToBar_.at(index)).birth == static_cast(index)); - bool iiIsPositive = (barcode_.at(indexToBar_.at(index + 1)).birth == - static_cast(index) + 1); - - if (iIsPositive && iiIsPositive) - _positive_vine_swap(index); - else if (!iIsPositive && !iiIsPositive) - _negative_vine_swap(index); - else if (iIsPositive && !iiIsPositive) - _positive_negative_vine_swap(index); - else - _negative_positive_vine_swap(index); -} - -template -inline const barcode_type &RU_matrix::get_current_barcode() { - return barcode_; -} - -template -inline void RU_matrix::print_matrices() { - boundary_type b; - - std::cout << "R:\n"; - for (unsigned int i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { - reducedMatrixR_.get_boundary(i, b); - if (b.empty()) { - std::cout << "-\n"; - } else { - for (unsigned int i : b) - std::cout << i << " "; - std::cout << "\n"; - b.clear(); - } - } - std::cout << "\n"; - - std::cout << "U:\n"; - for (unsigned int i = 0; i < mirrorMatrixU_.get_number_of_columns(); i++) { - mirrorMatrixU_.get_boundary(i, b); - if (b.empty()) { - std::cout << "-\n"; - } else { - for (unsigned int i : b) - std::cout << i << " "; - std::cout << "\n"; - b.clear(); - } - } - std::cout << "\n"; -} - -template -inline RU_matrix & -RU_matrix::operator=(RU_matrix other) { - std::swap(reducedMatrixR_, other.reducedMatrixR_); - std::swap(mirrorMatrixU_, other.mirrorMatrixU_); - std::swap(barcode_, other.barcode_); - std::swap(indexToBar_, other.indexToBar_); - return *this; -} - -template -inline void RU_matrix::_initialize_U() { - boundary_type id(1); - for (unsigned int i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { - id.at(0) = i; - mirrorMatrixU_.insert_column(i, Column_type(id)); - } -} - -template -inline void RU_matrix::_swap_at_index(index index) { - reducedMatrixR_.swap_at_indices(index, index + 1); - mirrorMatrixU_.swap_at_indices(index, index + 1); -} - -template -inline void RU_matrix::_add_to(index sourceIndex, - index targetIndex) { - reducedMatrixR_.add_to(sourceIndex, targetIndex); - mirrorMatrixU_.add_to(targetIndex, sourceIndex); -} - -template -inline void RU_matrix::_positive_vine_swap(index index) { - int iDeath = barcode_.at(indexToBar_.at(index)).death; - int iiDeath = barcode_.at(indexToBar_.at(index + 1)).death; - - if (get_dimension(index) == get_dimension(index + 1)) { - if (!mirrorMatrixU_.is_zero_cell(index, index + 1)) - mirrorMatrixU_.zero_cell(index, index + 1); - - if (iDeath != -1 && iiDeath != -1 && - !(reducedMatrixR_.is_zero_cell(iiDeath, index))) { - if (iDeath < iiDeath) { - _swap_at_index(index); - _add_to(iDeath, iiDeath); - - barcode_.at(indexToBar_.at(index)).birth = index + 1; - barcode_.at(indexToBar_.at(index + 1)).birth = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); - - return; - } - - if (iiDeath < iDeath) { - _swap_at_index(index); - _add_to(iiDeath, iDeath); - - return; - } - } - - _swap_at_index(index); - - if (iDeath != -1 || iiDeath == -1 || - reducedMatrixR_.is_zero_cell(iiDeath, index + 1)) { - barcode_.at(indexToBar_.at(index)).birth = index + 1; - barcode_.at(indexToBar_.at(index + 1)).birth = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); - } - - return; - } - - _swap_at_index(index); - - barcode_.at(indexToBar_.at(index)).birth = index + 1; - barcode_.at(indexToBar_.at(index + 1)).birth = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); -} - -template -inline void RU_matrix::_negative_vine_swap(index index) { - if (get_dimension(index) == get_dimension(index + 1) && - !mirrorMatrixU_.is_zero_cell(index, index + 1)) { - _add_to(index, index + 1); - _swap_at_index(index); - - if (barcode_.at(indexToBar_.at(index)).birth < - barcode_.at(indexToBar_.at(index + 1)).birth) { - barcode_.at(indexToBar_.at(index)).death = index + 1; - barcode_.at(indexToBar_.at(index + 1)).death = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); - - return; - } - - _add_to(index, index + 1); - - return; - } - - _swap_at_index(index); - - barcode_.at(indexToBar_.at(index)).death = index + 1; - barcode_.at(indexToBar_.at(index + 1)).death = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); -} - -template -inline void RU_matrix::_positive_negative_vine_swap(index index) { - if (get_dimension(index) == get_dimension(index + 1) && - !mirrorMatrixU_.is_zero_cell(index, index + 1)) - mirrorMatrixU_.zero_cell(index, index + 1); - - _swap_at_index(index); - - barcode_.at(indexToBar_.at(index)).birth = index + 1; - barcode_.at(indexToBar_.at(index + 1)).death = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); -} - -template -inline void RU_matrix::_negative_positive_vine_swap(index index) { - if (get_dimension(index) == get_dimension(index + 1) && - !mirrorMatrixU_.is_zero_cell(index, index + 1)) { - _add_to(index, index + 1); - _swap_at_index(index); - _add_to(index, index + 1); - - return; - } - - _swap_at_index(index); - - barcode_.at(indexToBar_.at(index)).death = index + 1; - barcode_.at(indexToBar_.at(index + 1)).birth = index; - std::swap(indexToBar_.at(index), indexToBar_.at(index + 1)); -} - -template -inline void swap(RU_matrix &matrix1, - RU_matrix &matrix2) { - std::swap(matrix1.reducedMatrixR_, matrix2.reducedMatrixR_); - std::swap(matrix1.mirrorMatrixU_, matrix2.mirrorMatrixU_); - matrix1.barcode_.swap(matrix2.barcode_); - matrix1.indexToBar_.swap(matrix2.indexToBar_); -} - -} // namespace Gudhi::multiparameter::mma - -#endif // RU_MATRIX_H diff --git a/multipers/multiparameter_module_approximation/set_column.h b/multipers/multiparameter_module_approximation/set_column.h deleted file mode 100644 index 82fd7d6e..00000000 --- a/multipers/multiparameter_module_approximation/set_column.h +++ /dev/null @@ -1,135 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef SETCOLUMN_H -#define SETCOLUMN_H - -#include -#include -#include - -#include "utilities.h" - -namespace Gudhi::multiparameter::mma { - -class Set_column -{ -public: - Set_column(); - Set_column(boundary_type& boundary); - Set_column(Set_column& column); - Set_column(Set_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(Set_column& column); - - Set_column& operator=(Set_column other); - - friend void swap(Set_column& col1, Set_column& col2); - -private: - int dim_; - std::set column_; -}; - -inline Set_column::Set_column() : dim_(0) -{} - -inline Set_column::Set_column(boundary_type &boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary.begin(), boundary.end()) -{} - -inline Set_column::Set_column(Set_column &column) - : dim_(column.dim_), - column_(column.column_) -{} - -inline Set_column::Set_column(Set_column &&column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)) -{} - -inline void Set_column::get_content(boundary_type &container) -{ - std::copy(column_.begin(), column_.end(), std::back_inserter(container)); -} - -inline bool Set_column::contains(unsigned int value) const -{ - return column_.find(value) != column_.end(); -} - -inline bool Set_column::is_empty() -{ - return column_.empty(); -} - -inline dimension_type Set_column::get_dimension() const -{ - return dim_; -} - -inline int Set_column::get_pivot() -{ - if (column_.empty()) return -1; - return *(column_.rbegin()); -} - -inline void Set_column::clear() -{ - column_.clear(); -} - -inline void Set_column::clear(unsigned int value) -{ - column_.erase(value); -} - -inline void Set_column::reorder(std::vector &valueMap) -{ - std::set newSet; - for (const unsigned int& v : column_) newSet.insert(valueMap.at(v)); - column_.swap(newSet); -} - -inline void Set_column::add(Set_column &column) -{ - for (const unsigned int& v : column.column_){ - if (column_.find(v) != column_.end()) - column_.erase(v); - else - column_.insert(v); - } -} - -inline Set_column &Set_column::operator=(Set_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - return *this; -} - -inline void swap(Set_column& col1, Set_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); -} - -} //namespace Vineyard - -#endif // SETCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h b/multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h deleted file mode 100644 index 003b1b25..00000000 --- a/multipers/multiparameter_module_approximation/structure_higher_dim_barcode.h +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence class, renaming and cleanup. - */ -/** - * @file structure_higher_dim_barcode.h - * @author David Loiseaux, Hannah Schreiber - * @brief Structures to handle higher dimensional persistence. - */ - -#ifndef STRUCTURE_HIGHER_DIM_BARCODE_H_INCLUDED -#define STRUCTURE_HIGHER_DIM_BARCODE_H_INCLUDED - -#include - -unsigned int get_index_from_position_and_size( - const std::vector &position, - const std::vector &size) -{ - unsigned int indice = 0; - assert(position.size() == size.size() && - "Position and Size vector must be of the same size !"); - unsigned int last_product = 1; - for (unsigned int i = 0; i < position.size(); i++){ - indice += last_product * position[i]; - last_product *= size[i]; - } - return indice; -} - -#endif // STRUCTURE_HIGHER_DIM_BARCODE_H_INCLUDED diff --git a/multipers/multiparameter_module_approximation/unordered_set_column.h b/multipers/multiparameter_module_approximation/unordered_set_column.h deleted file mode 100644 index b5629e78..00000000 --- a/multipers/multiparameter_module_approximation/unordered_set_column.h +++ /dev/null @@ -1,166 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef UNORDEREDSETCOLUMN_H -#define UNORDEREDSETCOLUMN_H - -#include -#include -#include -#include - -#include "utilities.h" - -namespace Vineyard { - -class Unordered_set_column -{ -public: - Unordered_set_column(); - Unordered_set_column(boundary_type& boundary); - Unordered_set_column(Unordered_set_column& column); - Unordered_set_column(Unordered_set_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(Unordered_set_column& column); - - Unordered_set_column& operator=(Unordered_set_column other); - - friend void swap(Unordered_set_column& col1, Unordered_set_column& col2); - -private: - int dim_; - std::unordered_set column_; - bool pivotChanged_; - int pivot_; -}; - -inline Unordered_set_column::Unordered_set_column() - : dim_(0), pivotChanged_(false), pivot_(-1) -{} - -inline Unordered_set_column::Unordered_set_column(boundary_type &boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary.begin(), boundary.end()), - pivotChanged_(false), - pivot_(boundary.size() == 0 ? -1 : *std::max_element(boundary.begin(), boundary.end())) -{} - -inline Unordered_set_column::Unordered_set_column(Unordered_set_column &column) - : dim_(column.dim_), - column_(column.column_), - pivotChanged_(column.pivotChanged_), - pivot_(column.pivot_) -{} - -inline Unordered_set_column::Unordered_set_column(Unordered_set_column &&column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)), - pivotChanged_(std::exchange(column.pivotChanged_, 0)), - pivot_(std::exchange(column.pivot_, 0)) -{} - -inline void Unordered_set_column::get_content(boundary_type &container) -{ - std::copy(column_.begin(), column_.end(), std::back_inserter(container)); - std::sort(container.begin(), container.end()); -} - -inline bool Unordered_set_column::contains(unsigned int value) const -{ - return column_.find(value) != column_.end(); -} - -inline bool Unordered_set_column::is_empty() -{ - return column_.empty(); -} - -inline dimension_type Unordered_set_column::get_dimension() const -{ - return dim_; -} - -inline int Unordered_set_column::get_pivot() -{ - if (pivotChanged_){ - pivot_ = column_.size() == 0 ? - -1 - : *std::max_element(column_.begin(), column_.end()); - pivotChanged_ = false; - } - - return pivot_; -} - -inline void Unordered_set_column::clear() -{ - column_.clear(); - pivot_ = -1; - pivotChanged_ = false; -} - -inline void Unordered_set_column::clear(unsigned int value) -{ - column_.erase(value); - if (static_cast(value) == pivot_) pivotChanged_ = true; -} - -inline void Unordered_set_column::reorder(std::vector &valueMap) -{ - std::unordered_set newSet; - for (const unsigned int& v : column_) newSet.insert(valueMap.at(v)); - column_.swap(newSet); - pivotChanged_ = true; -} - -inline void Unordered_set_column::add(Unordered_set_column &column) -{ - for (const unsigned int& v : column.column_){ - if (column_.find(v) != column_.end()){ - column_.erase(v); - if (static_cast(v) == pivot_) pivotChanged_ = true; - } else { - column_.insert(v); - if (static_cast(v) > pivot_){ - pivot_ = v; - pivotChanged_ = false; - } - } - } -} - -inline Unordered_set_column &Unordered_set_column::operator=(Unordered_set_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - std::swap(pivotChanged_, other.pivotChanged_); - std::swap(pivot_, other.pivot_); - return *this; -} - -inline void swap(Unordered_set_column& col1, Unordered_set_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); - std::swap(col1.pivotChanged_, col2.pivotChanged_); - std::swap(col1.pivot_, col2.pivot_); -} - -} //namespace Vineyard - -#endif // UNORDEREDSETCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/utilities.h b/multipers/multiparameter_module_approximation/utilities.h index b41ddd4e..66bf0e75 100644 --- a/multipers/multiparameter_module_approximation/utilities.h +++ b/multipers/multiparameter_module_approximation/utilities.h @@ -12,19 +12,22 @@ #define UTILITIES_H #include -#include -#include -#include +#include #include +#include #include +#include "../gudhi/gudhi/multi_simplex_tree_helpers.h" +#include "../gudhi/gudhi/Multi_persistence/Box.h" +#include "../gudhi/Persistence_slices_interface.h" + namespace Gudhi::multiparameter::mma { constexpr const bool verbose = false; using index = unsigned int; using value_type = double; -using filtration_type = Gudhi::multi_filtration::One_critical_filtration; +using filtration_type = multipers::tmp_interface::Filtration_value; using multifiltration_type = std::vector; using python_filtration_type = std::vector; using python_multifiltration_type = std::vector; @@ -34,9 +37,9 @@ using persistence_pair = std::pair; using boundary_type = std::vector; using boundary_matrix = std::vector; using permutation_type = std::vector; -using point_type = Gudhi::multi_filtration::One_critical_filtration; -using corner_type = Gudhi::multi_filtration::One_critical_filtration; -using corners_type = std::pair, std::vector>; +using point_type = Gudhi::multi_persistence::Box::Point_t; +// using corner_type = Gudhi::multi_filtration::One_critical_filtration; +// using corners_type = std::pair, std::vector>; // using python_bar = // std::pair, // std::vector>; // This type is for python @@ -116,17 +119,27 @@ struct MultiDiagram { // for python interface MultiDiagram(std::vector> &m) : multiDiagram(m) {} - using python_bar = std::pair, - std::vector>; // This type is for python + using python_fil = std::vector; + using python_bar = std::pair; // This type is for python std::vector get_points(const dimension_type dimension = -1) const { // dump for python interface std::vector out; out.reserve(multiDiagram.size()); for (const MultiDiagram_point &pt : multiDiagram) { if (dimension == -1 || pt.get_dimension() == dimension) { - if (pt.get_birth().size() > 0 && pt.get_death().size() > 0 && !pt.get_birth().is_plus_inf() && - !pt.get_death().is_minus_inf()) - out.push_back({pt.get_birth(), pt.get_death()}); + if (pt.get_birth().num_generators() > 0 && pt.get_death().num_generators() > 0 && + !pt.get_birth().is_plus_inf() && !pt.get_death().is_minus_inf()) { + const auto &b = pt.get_birth(); + const auto &d = pt.get_death(); + assert(b.num_parameters() == d.num_parameters()); + python_fil first(b.num_parameters()); + python_fil second(b.num_parameters()); + for (unsigned int i = 0; i < b.num_parameters(); ++i) { + first[i] = b(0, i); + second[i] = d(0, i); + } + out.push_back(std::make_pair(std::move(first), std::move(second))); + } } } /* out.shrink_to_fit(); */ @@ -139,10 +152,10 @@ struct MultiDiagram { // for python interface out.reserve(multiDiagram.size()); for (const MultiDiagram_point &pt : multiDiagram) { if (dimension == -1 || pt.get_dimension() == dimension) { - const auto &b = pt.get_birth().template as_type(); - const auto &d = pt.get_death().template as_type(); + const auto &b = pt.get_birth(); + const auto &d = pt.get_death(); assert(!(b.is_plus_inf() || b.is_minus_inf() || d.is_plus_inf() || d.is_minus_inf())); - out.push_back({b[0], d[0], b[1], d[1]}); + out.emplace_back(std::initializer_list{b(0, 0), d(0, 0), b(0, 1), d(0, 1)}); } } out.shrink_to_fit(); @@ -202,8 +215,8 @@ struct MultiDiagrams { a = -inf; b = -inf; } else { - a = pt.get_birth()[0]; - b = pt.get_birth()[1]; + a = pt.get_birth()(0, 0); + b = pt.get_birth()(0, 1); } if (pt.get_death().is_plus_inf()) { c = inf; @@ -213,8 +226,8 @@ struct MultiDiagrams { c = 0; d = 0; } else { - c = pt.get_death()[0]; - d = pt.get_death()[1]; + c = pt.get_death()(0, 0); + d = pt.get_death()(0, 1); } /* out[i].push_back({pt.get_birth()[0], pt.get_death()[0], * pt.get_birth()[1], pt.get_death()[1],static_cast(j)}); */ @@ -224,10 +237,10 @@ struct MultiDiagrams { return out; } - using __for_python_plot_type = std::pair>, std::vector>; + using _for_python_plot_type = std::pair>, std::vector>; - __for_python_plot_type _for_python_plot(dimension_type dimension = -1, value_type min_persistence = 0) { - __for_python_plot_type out; + _for_python_plot_type _for_python_plot(dimension_type dimension = -1, value_type min_persistence = 0) { + _for_python_plot_type out; auto &bars = out.first; auto &summand_idx = out.second; bars.reserve(this->multiDiagrams.size() * this->multiDiagrams[0].size() * 2); @@ -235,13 +248,13 @@ struct MultiDiagrams { for (const MultiDiagram &multiDiagram : this->multiDiagrams) { unsigned int count = 0; for (const MultiDiagram_point &bar : multiDiagram) { - const auto &birth = bar.get_birth().template as_type(); - const auto &death = bar.get_death().template as_type(); - if ((dimension == -1 || bar.get_dimension() == dimension) && birth.size() > 1 && death.size() > 1 && - (death[0] > birth[0] + min_persistence)) { - // Checking >1 ensure that filtration is not inf or -inf or nan. - bars.push_back({birth[0], death[0]}); - bars.push_back({birth[1], death[1]}); + const auto &birth = bar.get_birth(); + const auto &death = bar.get_death(); + if ((dimension == -1 || bar.get_dimension() == dimension) && birth.is_finite() && death.is_finite() && + (death(0, 0) > birth(0, 0) + min_persistence)) { + // Checking is_finite ensures that filtration is not inf or -inf or nan. + bars.push_back(std::pair(birth(0, 0), death(0, 0))); + bars.push_back(std::pair(birth(0, 1), death(0, 1))); summand_idx.push_back(count); } count++; @@ -258,6 +271,7 @@ struct MultiDiagrams { iterator end() const { return this->multiDiagrams.end(); } + using python_fil = std::vector; using python_bar = std::pair, std::vector>; // This type is for python using barcodes = std::vector>; @@ -271,7 +285,16 @@ struct MultiDiagrams { for (unsigned int i = 0; i < nlines; i++) { for (unsigned int j = 0; j < nsummands; j++) { const MultiDiagram_point &pt = this->multiDiagrams[i][j]; - out[i][j] = {pt.get_birth(), pt.get_death()}; + const auto &b = pt.get_birth(); + const auto &d = pt.get_death(); + assert(b.num_parameters() == d.num_parameters()); + python_fil first(b.num_parameters()); + python_fil second(b.num_parameters()); + for (unsigned int i = 0; i < b.num_parameters(); ++i) { + first[i] = b(0, i); + second[i] = d(0, i); + } + out[i][j] = std::make_pair(std::move(first), std::move(second)); } } return out; @@ -296,7 +319,7 @@ struct MultiDiagrams { void inline threshold_up(point_type &point, const Gudhi::multi_persistence::Box &box, const point_type &basepoint) { - Gudhi::multi_filtration::One_critical_filtration point_(point); + point_type point_(point); // if (is_smaller(point, box.get_upper_corner())) return; if (point_ <= box.get_upper_corner()) return; @@ -380,6 +403,8 @@ void inline threshold_down(point_type &point, for (unsigned int i = 0; i < point.size(); i++) point[i] += threshold; } + + } // namespace Gudhi::multiparameter::mma //// Different implementations of the matrix columns. Set seems to be the diff --git a/multipers/multiparameter_module_approximation/vector_column.h b/multipers/multiparameter_module_approximation/vector_column.h deleted file mode 100644 index 9457fff8..00000000 --- a/multipers/multiparameter_module_approximation/vector_column.h +++ /dev/null @@ -1,223 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef VECTORCOLUMN_H -#define VECTORCOLUMN_H - -#include -#include -#include - -#include "utilities.h" - -namespace Vineyard { - -class Vector_column -{ -public: - Vector_column(); - Vector_column(boundary_type& boundary); - Vector_column(Vector_column& column); - Vector_column(Vector_column&& column) noexcept; - - void get_content(boundary_type& container); - bool contains(unsigned int value) const; - bool is_empty(); - dimension_type get_dimension() const; - int get_pivot(); - void clear(); - void clear(unsigned int value); - void reorder(std::vector& valueMap); - void add(Vector_column& column); - - Vector_column& operator=(Vector_column other); - - friend void swap(Vector_column& col1, Vector_column& col2); - -private: - int dim_; - std::vector column_; - std::unordered_set erasedValues_; - - void _cleanValues(); -}; - -inline Vector_column::Vector_column() : dim_(0) -{} - -inline Vector_column::Vector_column(boundary_type &boundary) - : dim_(boundary.size() == 0 ? 0 : boundary.size() - 1), - column_(boundary) -{} - -inline Vector_column::Vector_column(Vector_column &column) - : dim_(column.dim_), - column_(column.column_), - erasedValues_(column.erasedValues_) -{} - -inline Vector_column::Vector_column(Vector_column &&column) noexcept - : dim_(std::exchange(column.dim_, 0)), - column_(std::move(column.column_)), - erasedValues_(std::move(column.erasedValues_)) -{} - -inline void Vector_column::get_content(boundary_type &container) -{ - _cleanValues(); - std::copy(column_.begin(), column_.end(), std::back_inserter(container)); -} - -inline bool Vector_column::contains(unsigned int value) const -{ - if (erasedValues_.find(value) != erasedValues_.end()) return false; - - for (unsigned int v : column_){ - if (v == value) return true; - } - return false; -} - -inline bool Vector_column::is_empty() -{ - _cleanValues(); - return column_.empty(); -} - -inline dimension_type Vector_column::get_dimension() const -{ - return dim_; -} - -inline int Vector_column::get_pivot() -{ - while (!column_.empty() && - erasedValues_.find(column_.back()) != erasedValues_.end()) { - erasedValues_.erase(column_.back()); - column_.pop_back(); - } - - if (column_.empty()) return -1; - - return column_.back(); -} - -inline void Vector_column::clear() -{ - column_.clear(); - erasedValues_.clear(); -} - -inline void Vector_column::clear(unsigned int value) -{ - erasedValues_.insert(value); -} - -inline void Vector_column::reorder(std::vector &valueMap) -{ - std::vector newColumn; - for (unsigned int& v : column_) { - if (erasedValues_.find(v) == erasedValues_.end()) - newColumn.push_back(valueMap.at(v)); - } - std::sort(newColumn.begin(), newColumn.end()); - erasedValues_.clear(); - column_.swap(newColumn); -} - -inline void Vector_column::add(Vector_column &column) -{ - if (column.is_empty()) return; - if (column_.empty()){ - std::copy(column.column_.begin(), column.column_.end(), std::back_inserter(column_)); - return; - } - - std::vector newColumn; - - std::vector::iterator itToAdd = column.column_.begin(); - std::vector::iterator itTarget = column_.begin(); - unsigned int valToAdd = *itToAdd; - unsigned int valTarget = *itTarget; - - while (itToAdd != column.column_.end() && itTarget != column_.end()) - { - while (itToAdd != column.column_.end() && - column.erasedValues_.find(valToAdd) != column.erasedValues_.end()) { - itToAdd++; - valToAdd = *itToAdd; - } - - while (itTarget != column_.end() && - erasedValues_.find(valTarget) != erasedValues_.end()) { - itTarget++; - valTarget = *itTarget; - } - - if (itToAdd != column.column_.end() && itTarget != column_.end()){ - if (valToAdd == valTarget){ - itTarget++; - itToAdd++; - } else if (valToAdd < valTarget){ - newColumn.push_back(valToAdd); - itToAdd++; - } else { - newColumn.push_back(valTarget); - itTarget++; - } - } - - valToAdd = *itToAdd; - valTarget = *itTarget; - } - - while (itToAdd != column.column_.end()){ - newColumn.push_back(*itToAdd); - itToAdd++; - } - - while (itTarget != column_.end()){ - newColumn.push_back(*itTarget); - itTarget++; - } - - column_.swap(newColumn); - erasedValues_.clear(); -} - -inline Vector_column &Vector_column::operator=(Vector_column other) -{ - std::swap(dim_, other.dim_); - std::swap(column_, other.column_); - std::swap(erasedValues_, other.erasedValues_); - return *this; -} - -inline void Vector_column::_cleanValues() -{ - std::vector newColumn; - for (unsigned int v : column_){ - if (erasedValues_.find(v) == erasedValues_.end()) - newColumn.push_back(v); - } - erasedValues_.clear(); - column_.swap(newColumn); -} - -inline void swap(Vector_column& col1, Vector_column& col2) -{ - std::swap(col1.dim_, col2.dim_); - col1.column_.swap(col2.column_); - std::swap(col1.erasedValues_, col2.erasedValues_); -} - -} //namespace Vineyard - -#endif // VECTORCOLUMN_H diff --git a/multipers/multiparameter_module_approximation/vector_matrix.h b/multipers/multiparameter_module_approximation/vector_matrix.h deleted file mode 100644 index 1a4188e0..00000000 --- a/multipers/multiparameter_module_approximation/vector_matrix.h +++ /dev/null @@ -1,331 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Hannah Schreiber - * - * Copyright (C) 2022 Inria - * - * Modification(s): - * - YYYY/MM Author: Description of the modification - */ - -#ifndef VECTOR_MATRIX_H -#define VECTOR_MATRIX_H - -#include -#include -#include -#include -#include - -#include "utilities.h" //type definitions - -namespace Gudhi::multiparameter::mma { - -template -class Vector_matrix -{ -public: - Vector_matrix(); - Vector_matrix(std::vector& orderedBoundaries); - Vector_matrix(unsigned int numberOfColumns); - Vector_matrix(Vector_matrix& matrixToCopy); - Vector_matrix(Vector_matrix&& other) noexcept; - - void insert_boundary(index columnIndex, boundary_type& boundary); - void get_boundary(index columnIndex, boundary_type& container); - void insert_column(index columnIndex, Column_type column); - Column_type& get_column(index columnIndex); - - void reduce(barcode_type& barcode); - - index get_last_index() const; - dimension_type get_max_dim() const; - unsigned int get_number_of_columns() const; - - void swap_columns(index columnIndex1, index columnIndex2); - void swap_rows(index rowIndex1, index rowIndex2); - void swap_at_indices(index index1, index index2); - - void zero_cell(index columnIndex, index rowIndex); - void zero_column(index columnIndex); - - void add_to(index sourceColumnIndex, index targetColumnIndex); - bool is_zero_cell(index columnIndex, index rowIndex) const; - bool is_zero_column(index columnIndex); - - dimension_type get_column_dimension(index columnIndex) const; - - Vector_matrix& operator=(Vector_matrix other); - template - friend void swap(Vector_matrix& matrix1, - Vector_matrix& matrix2); - -private: - std::vector matrix_; - dimension_type maxDim_; - std::vector indexToRow_; - std::vector rowToIndex_; - bool rowSwapped_; - - void _clear_column(index columnIndex); - void _orderRows(); -}; - -template -inline Vector_matrix::Vector_matrix() : maxDim_(0), rowSwapped_(false) -{} - -template -inline Vector_matrix::Vector_matrix( - std::vector& orderedBoundaries) : maxDim_(0), rowSwapped_(false) -{ - matrix_.resize(orderedBoundaries.size()); - indexToRow_.resize(orderedBoundaries.size()); - rowToIndex_.resize(orderedBoundaries.size()); - for (unsigned int i = 0; i < orderedBoundaries.size(); i++){ - boundary_type& b = orderedBoundaries.at(i); - matrix_.at(i) = Column_type(b); - if (maxDim_ < static_cast(b.size()) - 1) maxDim_ = b.size() - 1; - indexToRow_.at(i) = i; - rowToIndex_.at(i) = i; - } - if (maxDim_ == -1) maxDim_ = 0; -} - -template -inline Vector_matrix::Vector_matrix(unsigned int numberOfColumns) - : maxDim_(0), rowSwapped_(false) -{ - matrix_.resize(numberOfColumns); - indexToRow_.resize(numberOfColumns); - rowToIndex_.resize(numberOfColumns); - for (unsigned int i = 0; i < numberOfColumns; i++){ - indexToRow_.at(i) = i; - rowToIndex_.at(i) = i; - } -} - -template -inline Vector_matrix::Vector_matrix( - Vector_matrix& matrixToCopy) : maxDim_(0), rowSwapped_(false) -{ - matrix_.resize(matrixToCopy.get_number_of_columns()); - indexToRow_.resize(matrix_.size()); - rowToIndex_.resize(matrix_.size()); - for (unsigned int i = 0; i < matrix_.size(); i++){ - matrix_.at(i) = Column_type(matrixToCopy.get_column(i)); - indexToRow_.at(i) = i; - rowToIndex_.at(i) = i; - } - maxDim_ = matrixToCopy.get_max_dim(); -} - -template -inline Vector_matrix::Vector_matrix(Vector_matrix &&other) noexcept - : matrix_(std::move(other.matrix_)), - maxDim_(std::exchange(other.maxDim_, 0)), - indexToRow_(std::move(other.indexToRow_)), - rowToIndex_(std::move(other.rowToIndex_)), - rowSwapped_(std::exchange(other.rowSwapped_, 0)) -{} - -template -inline void Vector_matrix::insert_boundary( - index columnIndex, boundary_type& boundary) -{ - if (rowSwapped_) _orderRows(); - - if (matrix_.size() <= columnIndex) { - for (int i = matrix_.size(); i <= columnIndex; i++){ - indexToRow_.push_back(i); - rowToIndex_.push_back(i); - } - matrix_.resize(columnIndex + 1); - } - matrix_.at(columnIndex) = Column_type(boundary); - if (maxDim_ < boundary.size() - 1) maxDim_ = boundary.size() - 1; -} - -template -inline void Vector_matrix::get_boundary( - index columnIndex, boundary_type& container) -{ - matrix_.at(columnIndex).get_content(container); - for (unsigned int& v : container) v = rowToIndex_.at(v); - std::sort(container.begin(), container.end()); -} - -template -inline void Vector_matrix::insert_column( - index columnIndex, Column_type column) -{ - if (rowSwapped_) _orderRows(); - - dimension_type dim = column.get_dimension(); - if (matrix_.size() <= columnIndex) { - for (unsigned int i = matrix_.size(); i <= columnIndex; i++){ - indexToRow_.push_back(i); - rowToIndex_.push_back(i); - } - matrix_.resize(columnIndex + 1); - } - std::swap(matrix_.at(columnIndex), column); - if (maxDim_ < dim) maxDim_ = dim; -} - -template -inline Column_type& Vector_matrix::get_column(index columnIndex) -{ - if (rowSwapped_) _orderRows(); - return matrix_.at(columnIndex); -} - -template -inline void Vector_matrix::reduce(barcode_type& barcode) -{ - if (rowSwapped_) _orderRows(); - std::unordered_map pivotsToColumn; - - for (int d = maxDim_; d > 0; d--){ - for (unsigned int i = 0; i < matrix_.size(); i++){ - if (!(matrix_.at(i).is_empty()) && matrix_.at(i).get_dimension() == d) - { - Column_type &curr = matrix_.at(i); - int pivot = curr.get_pivot(); - - while (pivot != -1 && pivotsToColumn.find(pivot) != pivotsToColumn.end()){ - curr.add(matrix_.at(pivotsToColumn.at(pivot))); - pivot = curr.get_pivot(); - } - - if (pivot != -1){ - pivotsToColumn.emplace(pivot, i); - _clear_column(pivot); - barcode.push_back(Bar(d - 1, pivot, i)); - } else { - _clear_column(i); - } - } - } - } -} - -template -inline index Vector_matrix::get_last_index() const -{ - return matrix_.size() - 1; -} - -template -inline dimension_type Vector_matrix::get_max_dim() const -{ - return maxDim_; -} - -template -inline unsigned int Vector_matrix::get_number_of_columns() const -{ - return matrix_.size(); -} - -template -inline void Vector_matrix::swap_columns(index columnIndex1, index columnIndex2) -{ - std::swap(matrix_.at(columnIndex1), matrix_.at(columnIndex2)); -} - -template -inline void Vector_matrix::swap_rows(index rowIndex1, index rowIndex2) -{ - rowSwapped_ = true; - std::swap(rowToIndex_.at(indexToRow_.at(rowIndex1)), rowToIndex_.at(indexToRow_.at(rowIndex2))); - std::swap(indexToRow_.at(rowIndex1), indexToRow_.at(rowIndex2)); -} - -template -inline void Vector_matrix::swap_at_indices(index index1, index index2) -{ - swap_columns(index1, index2); - swap_rows(index1, index2); -} - -template -inline void Vector_matrix::zero_cell(index columnIndex, index rowIndex) -{ - matrix_.at(columnIndex).clear(indexToRow_.at(rowIndex)); -} - -template -inline void Vector_matrix::zero_column(index columnIndex) -{ - _clear_column(columnIndex); -} - -template -inline void Vector_matrix::add_to(index sourceColumnIndex, index targetColumnIndex) -{ - matrix_.at(targetColumnIndex).add(matrix_.at(sourceColumnIndex)); -} - -template -inline bool Vector_matrix::is_zero_cell(index columnIndex, index rowIndex) const -{ - return !(matrix_.at(columnIndex).contains(indexToRow_.at(rowIndex))); -} - -template -inline bool Vector_matrix::is_zero_column(index columnIndex) -{ - return matrix_.at(columnIndex).is_empty(); -} - -template -inline dimension_type Vector_matrix::get_column_dimension(index columnIndex) const -{ - return matrix_.at(columnIndex).get_dimension(); -} - -template -inline Vector_matrix &Vector_matrix::operator=( - Vector_matrix other) -{ - std::swap(matrix_, other.matrix_); - std::swap(maxDim_, other.maxDim_); - std::swap(indexToRow_, other.indexToRow_); - std::swap(rowToIndex_, other.rowToIndex_); - std::swap(rowSwapped_, other.rowSwapped_); - return *this; -} - -template -inline void Vector_matrix::_clear_column(index columnIndex) -{ - matrix_.at(columnIndex).clear(); -} - -template -inline void Vector_matrix::_orderRows() -{ - for (unsigned int i = 0; i < matrix_.size(); i++){ - matrix_.at(i).reorder(rowToIndex_); - } - for (unsigned int i = 0; i < matrix_.size(); i++){ - indexToRow_.at(i) = i; - rowToIndex_.at(i) = i; - } - rowSwapped_ = false; -} - -template -inline void swap(Vector_matrix& matrix1, Vector_matrix& matrix2) -{ - std::swap(matrix1.matrix_, matrix2.matrix_); - std::swap(matrix1.maxDim_, matrix2.maxDim_); - std::swap(matrix1.indexToRow_, matrix2.indexToRow_); - std::swap(matrix1.rowToIndex_, matrix2.rowToIndex_); - std::swap(matrix1.rowSwapped_, matrix2.rowSwapped_); -} - -} //namespace Vineyard - -#endif // VECTOR_MATRIX_H diff --git a/multipers/multiparameter_module_approximation/vineyards.h b/multipers/multiparameter_module_approximation/vineyards.h deleted file mode 100644 index a98875c3..00000000 --- a/multipers/multiparameter_module_approximation/vineyards.h +++ /dev/null @@ -1,464 +0,0 @@ -/* This file is part of the MMA Library - https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. - * See file LICENSE for full license details. - * Author(s): Mathieu Carrière, David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Rewriting of the Vineyard_persistence class with new matrix data structure. - */ -/** - * @file vineyards.h - * @author Mathieu Carrière, David Loiseaux, Hannah Schreiber - * @brief Core vineyards functions. - */ - -#ifndef VINEYARDS_H_INCLUDED -#define VINEYARDS_H_INCLUDED - -#define set_insertion_sort - -#include -#include -#include -#include - -#include "debug.h" -#include "combinatory.h" - -#include "utilities.h" -//#include "ru_matrix.h" -//#include "list_column.h" - -namespace Gudhi::multiparameter::mma { - -struct Filtration_creator -{ - //assumes matrix in an valid filtration order and the all vertices - //are in the first cells of vertexOrder in the same order than in matrix - static void get_lower_star_filtration( - const boundary_matrix& matrix, - const std::vector& vertexOrder, - filtration_type& resultingFilter) - { - resultingFilter.resize(matrix.size()); - int currentVertexPosition = 0; - - for (unsigned int i = 0; i < matrix.size(); i++) - { - const boundary_type& b = matrix.at(i); - if (b.size() == 0){ - resultingFilter.at(i) = vertexOrder.at(currentVertexPosition++); - } else { - resultingFilter.at(i) = resultingFilter.at(b.front()); - for (unsigned int j = 1; j < b.size(); j++) - resultingFilter.at(i) = std::max(resultingFilter.at(i), - resultingFilter.at(b.at(j))); - } - } - } - static void complete_lower_star_filters_list( - const boundary_matrix &matrix, - std::vector &filtersList) - { - - for (filtration_type &fi : filtersList){ // gives a value to the lower star simplices - unsigned int n = fi.size(); // the number of simplicies that already have a filtration value - fi.resize(matrix.size()); - for( unsigned int simplex = n; simplex < matrix.size(); simplex++ ){ - const boundary_type& b = matrix.at(simplex); - - // if(b.size() == 0) - // std::cout << " Invalid Filtration !!\n"; // vertices must have a filtration - - value_type value = fi.at(b.front()); - for (unsigned int j = 1; j < b.size(); j++) - value = std::max(value, fi[b[j]]); - - fi[simplex] = value; - } - } - } - - -}; - -template*/> -/** - * @brief Stores the variables to be able to apply a vineyard update - * - */ -class Vineyard_persistence -{ -public: - Vineyard_persistence(); - Vineyard_persistence( - const boundary_matrix& matrix, - bool verbose = false); - Vineyard_persistence( - const boundary_matrix& matrix, - const filtration_type& filter, - bool verbose = false); - Vineyard_persistence( - const Vineyard_matrix_type& matrix, - bool verbose = false); - Vineyard_persistence( - const Vineyard_matrix_type& matrix, - const filtration_type& filter, - bool verbose = false); - Vineyard_persistence( - const Vineyard_matrix_type& matrix, - const filtration_type& filter, - const permutation_type& permutation, - bool verbose = false); - - void initialize_barcode(); - void update(filtration_type &newFilter); - const diagram_type &get_diagram(); - void display_diagram(); - void display_filtration(); - - Vineyard_persistence& operator=(Vineyard_persistence other); - -private: - Vineyard_matrix_type matrix_; - permutation_type currentToOriginalPositions_; //new pos to origin pos - diagram_type diagram_; - filtration_type filter_; - const bool verbose_; - - value_type _get_current_filtration_value(index index); - void _sort_matrix(const boundary_matrix& matrix); - void _initialize_permutations(); - void _initialize_filter(); -// void _update_old(filtration_type &newFilter); -}; - -template -inline Vineyard_persistence::Vineyard_persistence() -{} - -template -inline Vineyard_persistence::Vineyard_persistence( - const boundary_matrix& matrix, - bool verbose) - : matrix_(matrix), - currentToOriginalPositions_(matrix.size()), - filter_(matrix.size()), - verbose_(verbose) -{ Debug::Timer timer("Creating matrix... ",verbose_); -// if (verbose_) std::cout << "Creating matrix ..." << std::flush; - - _initialize_filter(); - _sort_matrix(matrix); - - if constexpr (Debug::debug){ - Debug::disp_vect(filter_); - matrix_.print_matrices(); - } - -// if (verbose_) std::cout << " Done !" << std::endl; -} - -//assumes filter[i] corresponds to filtration value of matrix[i] -template -inline Vineyard_persistence::Vineyard_persistence( - const boundary_matrix& matrix, - const filtration_type& filter, - bool verbose) - : currentToOriginalPositions_(matrix.size()), - filter_(filter), - verbose_(verbose) -{ Debug::Timer timer("Creating matrix... ",verbose_); -// if (verbose_) std::cout << "Creating matrix ..." << std::flush; - - _sort_matrix(matrix); - - if constexpr (Debug::debug){ - Debug::disp_vect(filter_); - matrix_.print_matrices(); - } - -// if (verbose_) std::cout << " Done !" << std::endl; -} - -//copy, i.e. assumes matrix sorted -template -inline Vineyard_persistence::Vineyard_persistence( - const Vineyard_matrix_type& matrix, - bool verbose) - : matrix_(matrix), - currentToOriginalPositions_(matrix.size()), - filter_(matrix.size()), - verbose_(verbose) -{ Debug::Timer timer("Creating matrix... ",verbose_); -// if (verbose_) std::cout << "Creating matrix ..." << std::flush; - - _initialize_permutations(); - _initialize_filter(); - - if constexpr (Debug::debug){ - Debug::disp_vect(filter_); - matrix_.print_matrices(); - } - -// if (verbose_) std::cout << " Done !" << std::endl; -} - -//copy, i.e. assumes matrix sorted and filter[i] corresponds to -//filtration value of matrix[i] -template -inline Vineyard_persistence::Vineyard_persistence( - const Vineyard_matrix_type& matrix, - const filtration_type& filter, - bool verbose) - : matrix_(matrix), - currentToOriginalPositions_(matrix.size()), - filter_(filter), - verbose_(verbose) -{ Debug::Timer timer("Creating matrix... ",verbose_); -// if (verbose_) std::cout << "Creating matrix ..." << std::flush; - - _initialize_permutations(); - - if constexpr (Debug::debug){ - Debug::disp_vect(filter_); - matrix_.print_matrices(); - } - -// if (verbose_) std::cout << " Done !" << std::endl; -} - -//copy, i.e. assumes matrix sorted and filter[permutation[i]] -//corresponds to filtration value of matrix[i] -template -inline Vineyard_persistence::Vineyard_persistence( - const Vineyard_matrix_type& matrix, - const filtration_type& filter, - const permutation_type& permutation, //new to original - bool verbose) - : matrix_(matrix), - currentToOriginalPositions_(permutation), - filter_(filter), - verbose_(verbose) -{ Debug::Timer timer("Creating matrix",verbose_); -// if (verbose_) std::cout << "Creating matrix ..." << std::flush; - - if constexpr (Debug::debug){ - Debug::disp_vect(filter_); - matrix_.print_matrices(); - } - -// if (verbose_) std::cout << " Done !" << std::endl; -} - -/** - * @brief Computes the first barcode, and fills the variables in the class. - * @param verbose - * @param debug - */ -template -inline void Vineyard_persistence::initialize_barcode() -{ - if (verbose_) { - std::cout << "Initializing barcode"; - } - auto elapsed = clock(); - - matrix_.initialize(); - - if (verbose_) - { - elapsed = clock() - elapsed; - std::cout << "... Done ! It took " << ((float)elapsed) / CLOCKS_PER_SEC - << " seconds." << std::endl; - } -} - -// template -// inline void Vineyard_persistence::update( -// filtration_type &newFilter) -// { -// _update_old(newFilter); -// } - -/** - * @brief Gets diagram from RU decomposition. - */ -template -inline const diagram_type& Vineyard_persistence::get_diagram() -{ - const barcode_type& barcode = matrix_.get_current_barcode(); - unsigned int size = barcode.size(); - - diagram_.resize(size); - - for (unsigned int i = 0; i < size; i++){ - const Bar& bar = barcode.at(i); - Diagram_point& point = diagram_.at(i); - point.dim = bar.dim; - point.birth = _get_current_filtration_value(bar.birth); - point.death = (bar.death == -1 ? - inf - : _get_current_filtration_value(bar.death)); - } - - return diagram_; -} - -template -inline void Vineyard_persistence::display_diagram() -{ - for (const Diagram_point& point : diagram_){ - std::cout << point.dim << " " << point.birth << " " - << point.death << std::endl; - } -} - -template -inline void Vineyard_persistence::display_filtration() -{ - std::cout << "filter:\n"; - for (double filtValue : filter_) - std::cout << filtValue << " "; - std::cout << std::endl; -} - -template -inline Vineyard_persistence& -Vineyard_persistence::operator=( - Vineyard_persistence other) -{ - std::swap(matrix_, other.matrix_); - std::swap(currentToOriginalPositions_, other.currentToOriginalPositions_); - std::swap(diagram_, other.diagram_); - std::swap(filter_, other.filter_); - return *this; -} - -template -inline value_type -Vineyard_persistence::_get_current_filtration_value( - index index) -{ - return filter_.at(currentToOriginalPositions_.at(index)); -} - -template -inline void Vineyard_persistence::_sort_matrix( - const boundary_matrix &matrix) -{ -// auto is_strict_less_than = [&matrix, this]( -// unsigned int index1, unsigned int index2) -// { -// if (matrix.at(index1).size() != matrix.at(index2).size()) -// return matrix.at(index1).size() < matrix.at(index2).size(); -// return filter_.at(index1) < filter_.at(index2); -// }; - auto is_less_filtration = [this]( // Dimension is already assumed to be sorted - unsigned int index1, unsigned int index2) - { - return filter_.at(index1) < filter_.at(index2); - }; - - permutation_type permutationInv(currentToOriginalPositions_.size()); - _initialize_permutations(); -// {Debug::Timer timer("Sorting matrix ...", verbose_); -// std::sort(currentToOriginalPositions_.begin(), currentToOriginalPositions_.end(), is_strict_less_than); - unsigned int iterator = 1; - while(iterator < matrix.size() ){ - auto first = iterator-1; - while(iterator < matrix.size() && matrix.at(iterator).size() == matrix.at(iterator-1).size()){ - iterator++; - } - std::sort(currentToOriginalPositions_.begin()+first , currentToOriginalPositions_.begin() + iterator, is_less_filtration ); - iterator++; - } -// } -// {Debug::Timer timer("Initialisation permutation ...", verbose_); - for (unsigned int i = 0; i < permutationInv.size(); i++) - permutationInv.at(currentToOriginalPositions_.at(i)) = i; -// } - boundary_matrix sorted(matrix.size()); -// {Debug::Timer timer("Transfering matrix ...", verbose_); - for (unsigned int i = 0; i < matrix.size(); i++){ - boundary_type& b = sorted.at(permutationInv.at(i)); - b = matrix.at(i); - for (index& id : b) - id = permutationInv.at(id); - std::sort(b.begin(), b.end()); - } -// } - - matrix_ = Vineyard_matrix_type(sorted); -} - -template -inline void Vineyard_persistence::_initialize_permutations() -{ - for (unsigned int i = 0; i < currentToOriginalPositions_.size(); i++) - currentToOriginalPositions_.at(i) = i; -} - -template -inline void Vineyard_persistence::_initialize_filter() -{ - for (unsigned int i = 0; i < filter_.size(); i++) - filter_.at(i) = i; -} - -template -inline void Vineyard_persistence::update( - filtration_type &newFilter) -{ - - filter_.swap(newFilter); - -// uint k = 0; - -#ifndef set_insertion_sort - int n = matrix_.get_number_of_simplices(); - bool sorted = false; - for (int i = n - 1; i > 0 && !sorted; i--) - { - sorted = true; - for (int j = 0; j < i; j++) - { - if (matrix_.get_dimension(j) == matrix_.get_dimension(j + 1) && - _get_current_filtration_value(j) > _get_current_filtration_value(j + 1)) - { - matrix_.vine_swap(j); - std::swap(currentToOriginalPositions_[j], - currentToOriginalPositions_[j + 1]); - sorted = false; -// if (verbose_) k++; - } - } - } -#else - unsigned int n = matrix_.get_number_of_simplices(); - for (unsigned int i = 0; i < n; i++) - { - unsigned int j = i; - while (j > 0 && matrix_.get_dimension(j) == matrix_.get_dimension(j - 1) && - _get_current_filtration_value(j) < _get_current_filtration_value(j - 1)) - { - matrix_.vine_swap(j - 1); - std::swap(currentToOriginalPositions_[j - 1], - currentToOriginalPositions_[j]); - j--; - } - } -#endif - - - - -// if (verbose_) -// std::cout << "Permuted " << k << "times, with " << n -// << " simplices." << std::endl; -}; - -} //namespace Vineyard - -#endif // VINEYARDS_H_INCLUDED diff --git a/multipers/multiparameter_module_approximation/vineyards_trajectories.h b/multipers/multiparameter_module_approximation/vineyards_trajectories.h deleted file mode 100644 index 9a2e4505..00000000 --- a/multipers/multiparameter_module_approximation/vineyards_trajectories.h +++ /dev/null @@ -1,649 +0,0 @@ -/* This file is part of the MMA Library - - * https://gitlab.inria.fr/dloiseau/multipers - which is released under MIT. See - * file LICENSE for full license details. Author(s): David Loiseaux - * - * Copyright (C) 2021 Inria - * - * Modification(s): - * - 2022/03 Hannah Schreiber: Integration of the new Vineyard_persistence - * class, renaming and cleanup. - * - 2022/05 Hannah Schreiber: Addition of Box class. - */ -/** - * @file vineyards_trajectories.h - * @author David Loiseaux, Hannah Schreiber - * @brief This file contains the functions related to trajectories of barcodes - * via vineyards. - */ - -#ifndef VINEYARDS_TRAJECTORIES_H_INCLUDED -#define VINEYARDS_TRAJECTORIES_H_INCLUDED - -#include -#include -// #include -#include -#include -#include -// #include "gudhi/Simplex_tree.h" - -#include "format_python-cpp.h" -#include "structure_higher_dim_barcode.h" -#include "vineyards.h" -// #include "combinatory.h" -#include "debug.h" - -#include "ru_matrix.h" -#include "utilities.h" -/*#include "heap_column.h"*/ -/*#include "list_column.h"*/ -// #include "list_column_2.h" -/*#include "vector_column.h"*/ -/*#include "set_column.h"*/ -/*#include "unordered_set_column.h"*/ -#include - -namespace Gudhi::multiparameter::mma { - -using Gudhi::multi_persistence::Box; -std::vector>> -compute_vineyard_barcode(boundary_matrix &boundaryMatrix, - const std::vector &filtersList, - value_type precision, Box &box, - bool threshold = false, bool multithread = false, - const bool verbose = false); - -std::vector> compute_vineyard_barcode_in_dimension( - boundary_matrix &boundaryMatrix, - const std::vector &filtersList, value_type precision, - Box &box, dimension_type dimension, bool threshold = false, - const bool verbose = false); - -void compute_vineyard_barcode_recursively( - std::vector>> &output, - Vineyard_persistence &persistence, - const boundary_matrix &boundaryMatrix, point_type &basepoint, - std::vector &position, unsigned int last, - filtration_type &filter, const std::vector &filtersList, - const value_type precision, const Box &box, - const std::vector &size, bool first = false, - bool threshold = false, bool multithread = false); - -void compute_vineyard_barcode_recursively_in_higher_dimension( - std::vector>> &output, - Vineyard_persistence &persistence, - const boundary_matrix &boundaryMatrix, const point_type &basepoint, - const std::vector &position, unsigned int last, - filtration_type &filter, const std::vector &filtersList, - const value_type precision, const Box &box, - const std::vector &size, bool threshold = false, - bool multithread = false); - -void get_filter_from_line(const point_type &lineBasepoint, - const std::vector &filterList, - filtration_type &newFilter, - const Box &box, bool ignoreLast = false); - -void threshold_up(point_type &point, const Box &box, - const point_type &basepoint = point_type(1, negInf)); - -void threshold_down(point_type &point, const Box &box, - const point_type &basepoint = point_type(1, negInf)); -// template -// bool is_smaller(const std::vector& x, const std::vector& y); -// template -// bool is_greater(const std::vector& x, const std::vector& y); -// boundary_matrix simplex_tree_to_boundary_matrix(Gudhi::Simplex_tree<> -// &simplexfiltration_value_typeree); - -// filtration_value_typeODO improve multithread -// Main function of vineyard computation. It computes the fibered barcodes of -// any multipersistence module, with exact matching. -// Input : -// B : sparse boundary matrix which is the converted -//simplextree by -// functions of format_python_cpp -// filters_list : [[filtration of dimension i for simplex s -// for s] for i] -// is the list of filters of each simplex of each filtration -// dimension -// precision : size of the line grid (ie. distance between -// 2 lines) box : [min, max] where min and max are points of R^n, and n is the -// dimension of the filter list. -// All of the bars along a line crossing this box -// will be computed threshold : If set to true, will intersect the bars with the -// box. -// Useful for plots / images -// multithread : if set to true, will compute the -// trajectories in parallel. filtration_value_typehis is a WIP; as this imply -// more memory operations, this is -// rarely significantly faster than the other implementation. -// OUTPUT : -// [[[(birth,death) for line] for summand] for dimension] -/** - * @brief Main function of vineyard computation. It computes the fibered - * barcodes of any multipersistence module, with exact matching. - * - * @param B Sparse boundary matrix of a chain complex - * @param filters_list associated filtration of @p B Format : - * [[filtration of dimension i for simplex s for s] for i] - * @param precision precision of the line grid ie. distance between two lines - * @param box [min, max] where min and max are points of \f$ \mathbb R^n \f$, - * and n is the dimension of the filter list. - * All of the bars along a line crossing this box will be computed - * @param threshold if set to true, will threshold the barcodes with the box - * @param multithread if set to true, will turn on the multithread flags of the - * code (WIP) - * @return vector>> List of barcodes along the lines - * intersecting the box. Format : [[[(birth,death) for line] for summand] for - * dimension] - */ -// Assumes matrix ordered by dimensions - -std::vector>> -compute_vineyard_barcode(boundary_matrix &boundaryMatrix, - const std::vector &filtersList, - value_type precision, Box &box, - bool threshold, bool multithread, bool verbose) { - Gudhi::multiparameter::mma::verbose = verbose; - // Checks if dimensions are compatibles - assert(!filtersList.empty() && "A non trivial filters list is needed !"); - assert(filtersList.size() == box.get_lower_corner().size() && - filtersList.size() == box.get_upper_corner().size() && - "Filtration and box must be of the same dimension"); - if constexpr (Debug::debug) { - for (unsigned int i = 1; i < boundaryMatrix.size(); i++) - assert(boundaryMatrix.at(i - 1).size() <= boundaryMatrix.at(i).size() && - "Boundary matrix has to be sorted by dimension!"); - } - - const unsigned int filtrationDimension = filtersList.size(); - if (verbose) - std::cout << "Filtration dimension : " << filtrationDimension << std::flush - << std::endl; - - unsigned int numberSimplices = boundaryMatrix.size(); - if (verbose) - std::cout << "Number of simplices : " << numberSimplices << std::endl; - - filtration_type filter(numberSimplices); // container of filters - - std::vector sizeLine(filtrationDimension - 1); - for (unsigned int i = 0; i < filtrationDimension - 1; i++) - sizeLine[i] = static_cast(std::ceil( - std::abs(box.get_upper_corner()[i] - box.get_lower_corner().back() - - box.get_lower_corner()[i] + box.get_upper_corner().back()) / - precision)); - - unsigned int numberOfLines = Combinatorics::prod(sizeLine); - if (verbose) - std::cout << "Precision : " << precision << std::endl; - if (verbose) - std::cout << "Number of lines : " << numberOfLines << std::endl; - - auto basePoint = box.get_lower_corner(); - for (unsigned int i = 0; i < basePoint.size() - 1; i++) - basePoint[i] -= box.get_upper_corner().back(); - basePoint.back() = 0; - - get_filter_from_line(basePoint, filtersList, filter, box, true); - // where is the cursor in the output matrix - std::vector position(filtrationDimension - 1, 0); - - if (filtersList[0].size() < numberSimplices) { - filtration_type tmp = filter; - Filtration_creator::get_lower_star_filtration(boundaryMatrix, tmp, filter); - } - - Vineyard_persistence persistence(boundaryMatrix, filter, - verbose); - persistence.initialize_barcode(); - auto &firstBarcode = persistence.get_diagram(); - - // filtered by dimension so last one is of maximal dimension - unsigned int maxDimension = firstBarcode.back().dim; - std::vector>> output(maxDimension + 1); - - std::vector numberOfFeaturesByDimension(maxDimension + 1); - for (unsigned int i = 0; i < firstBarcode.size(); i++) { - numberOfFeaturesByDimension[firstBarcode[i].dim]++; - } - - for (unsigned int i = 0; i < maxDimension + 1; i++) { - output[i] = std::vector>( - numberOfFeaturesByDimension[i], - std::vector(numberOfLines)); - } - - auto elapsed = clock(); - if (verbose) - std::cout << "Multithreading status : " << multithread << std::endl; - if (verbose) - std::cout << "Starting recursive vineyard loop..." << std::flush; - - compute_vineyard_barcode_recursively( - output, persistence, boundaryMatrix, basePoint, position, 0, filter, - filtersList, precision, box, sizeLine, true, threshold, multithread); - - elapsed = clock() - elapsed; - if (verbose) - std::cout << " Done ! It took " - << (static_cast(elapsed) / CLOCKS_PER_SEC) << " seconds." - << std::endl; - return output; -} - -// Same as vineyard_alt but only returns one dimension -// TODO : reduce computation by only computing this dimension instead of all of -// them -/** - * @brief Returns only one dimension of the \ref vineyard_alt code. - * - * @param B - * @param filters_list - * @param precision - * @param box - * @param dimension - * @param threshold - * @param verbose - * @param debug - * @return vector> - */ - -std::vector> compute_vineyard_barcode_in_dimension( - boundary_matrix &boundaryMatrix, - const std::vector &filtersList, value_type precision, - Box &box, dimension_type dimension, bool threshold, - const bool verbose) { - return compute_vineyard_barcode(boundaryMatrix, filtersList, precision, box, - threshold, false, verbose)[dimension]; -} - -// This is the core compute function of vineyard_alt. -// It updates and store in `output` the barcodes of a line, and calls itself -// on the next line until reaching the borders of the box -// INPUT : -// output : Where to store the barcodes. -// persistence : holding previous barcode information. -// basepoint : basepoint of the current line on the -// hyperplane {x_n=0}. position : index pointer of where to fill the output. -// last : which dimensions needs to be increased on this -// trajectory -// (for recursive trajectories). -// filter : container for filer of simplices. -// filters_list : holding the filtration value of each -// simplices. -// Format : [[filtration of simplex s in the kth filtration for s] -// for k]. -// precision : line grid scale (ie. distance between two -// consecutive lines). box : [min, max] where min and max are points of R^n, and -// n is the -// dimension of the filter list. -// All of the bars along a line crossing this box -// will be computed. size : size of the output matrix. first : true if it is the -// first barcode. In that case we don't need -// to call a vineyard update. -// threshold : if true, intersects bars with the box. -// multithread : if set to true, will compute the -// trajectories in parallel. This is a WIP; as this imply more memory -// operations, this is -// rarely significantly faster than the other implementation. -/** - * @brief Recursive version of \ref vineyard_alt. - * - * @param output - * @param persistence - * @param basepoint - * @param position - * @param last - * @param filter - * @param filters_list - * @param precision - * @param box - * @param size - * @param first - * @param threshold - * @param multithread - */ - -void compute_vineyard_barcode_recursively( - std::vector>> &output, - Vineyard_persistence &persistence, - const boundary_matrix &boundaryMatrix, point_type &basepoint, - std::vector &position, unsigned int last, - filtration_type &filter, const std::vector &filtersList, - const value_type precision, const Box &box, - const std::vector &size, bool first, bool threshold, - bool multithread) { - if (!first) { - get_filter_from_line(basepoint, filtersList, filter, box, true); - if (filtersList[0].size() < boundaryMatrix.size()) { - filtration_type tmp = filter; - Filtration_creator::get_lower_star_filtration(boundaryMatrix, tmp, - filter); - } - } - - // if (verbose && Debug::debug) Debug::disp_vect(basepoint); - - persistence.update(filter); // Updates the RU decomposition of persistence. - // Computes the diagram from the RU decomposition - const diagram_type &dgm = persistence.get_diagram(); - - // Fills the barcode of the line having the basepoint basepoint - unsigned int feature = 0; - int oldDim = 0; - - // %TODO parallelize this loop, last part is not compatible yet - for (unsigned int i = 0; i < dgm.size(); i++) { - dimension_type dim = dgm[i].dim; - value_type baseBirth = dgm[i].birth; - value_type baseDeath = dgm[i].death; - - unsigned int index = get_index_from_position_and_size(position, size); - point_type &birth = output[dim][feature][index].first; - point_type &death = output[dim][feature][index].second; - - // If the bar is trivial, we skip it - if (baseBirth != inf && baseBirth != baseDeath) { - birth.resize(filtersList.size()); - death.resize(filtersList.size()); - - // computes birth and death point from the bar and the basepoint of the - // line - for (unsigned int j = 0; j < filtersList.size() - 1; j++) { - birth[j] = basepoint[j] + baseBirth; - death[j] = basepoint[j] + baseDeath; - } - birth.back() = baseBirth; - death.back() = baseDeath; - - // Threshold birth and death if threshold is set to true - if (threshold && birth.back() != inf) { - threshold_down(birth, box, basepoint); - threshold_up(death, box, basepoint); - } - - // if (verbose) { - // std::cout << birth.back() << " " << death.back(); - // if (threshold) std::cout << ", threshold" << std::endl; - // else std::cout << ", no threshold" << std::endl; - // } - - // If this threshold has turned this bar to a trivial bar, we skip it - if (birth.back() >= death.back()) { - birth.clear(); - death.clear(); - } - } - - // If next bar is of upper dimension, or we reached the end, then we - // update the pointer index of where to fill the next bar in output. - if (i + 1 < dgm.size() && oldDim < dgm[i + 1].dim) { - oldDim = dgm[i + 1].dim; - feature = 0; - } else - feature++; - - // if (verbose) - // std::cout <<"Feature : " << feature << " dim : " << oldDim << - // std::endl; - } - - // recursive calls of bigger dims, minus current dim (to make less copies) - compute_vineyard_barcode_recursively_in_higher_dimension( - output, persistence, boundaryMatrix, basepoint, position, last, filter, - filtersList, precision, box, size, threshold, multithread); - - // We keep -last- on the same thread / memory as the previous call - // we reached a border and finished this path - if (size[last] - 1 == position[last]) - return; - - // If we didn't reached the end, go to the next line - basepoint[last] += precision; - position[last]++; - compute_vineyard_barcode_recursively( - output, persistence, boundaryMatrix, basepoint, position, last, filter, - filtersList, precision, box, size, false, threshold, multithread); -} - -// For persistence dimension higher than 3, this function will be called for -// Tree-like recursion of vineyard_alt. -/** - * @brief Subfonction of \ref vinyard_alt_recursive to handle dimensions - * greater than 3. - * - * @param output - * @param persistence - * @param basepoint - * @param position - * @param last - * @param filter - * @param filters_list - * @param precision - * @param box - * @param size - * @param threshold - * @param multithread - */ - -void compute_vineyard_barcode_recursively_in_higher_dimension( - std::vector>> &output, - Vineyard_persistence &persistence, - const boundary_matrix &boundaryMatrix, const point_type &basepoint, - const std::vector &position, unsigned int last, - filtration_type &filter, const std::vector &filtersList, - const value_type precision, const Box &box, - const std::vector &size, bool threshold, bool multithread) { - if (filtersList.size() > 1 && last + 2 < filtersList.size()) { - // if (verbose && Debug::debug) Debug::disp_vect(basepoint); - // if (verbose) std::cout << multithread << std::endl; - - if (multithread) { - /* #pragma omp parallel for */ - for (unsigned int i = last + 1; i < filtersList.size() - 1; i++) { - if (size[i] - 1 == position[i]) - continue; - // TODO check if it get deleted at each loop !! WARNING - auto copyPersistence = persistence; - auto copyBasepoint = basepoint; - auto copyPosition = position; - copyBasepoint[i] += precision; - copyPosition[i]++; - compute_vineyard_barcode_recursively( - output, copyPersistence, boundaryMatrix, copyBasepoint, - copyPosition, i, filter, filtersList, precision, box, size, false, - threshold, multithread); - } - } else { - // No need to copy when not multithreaded. - // Memory operations are slower than vineyard. - // %TODO improve trajectory of vineyard - auto copyPersistence = persistence; - auto copyBasepoint = basepoint; - auto copyPosition = position; - for (unsigned int i = last + 1; i < filtersList.size() - 1; i++) { - if (size[i] - 1 == position[i]) - continue; - copyPersistence = persistence; - copyBasepoint = basepoint; - copyPosition = position; - copyBasepoint[i] += precision; - copyPosition[i]++; - compute_vineyard_barcode_recursively( - output, copyPersistence, boundaryMatrix, copyBasepoint, - copyPosition, i, filter, filtersList, precision, box, size, false, - threshold, multithread); - } - } - } -} - -// INPUT : -// a slope 1 line is characterized by its intersection with {x_n=0} named -// line_basepoint. -// filter_list is : for each coordinate i, and simplex j filter_list[i,j] -//is -// the filtration value of simplex j on line induced by [0,e_i] -// OUTPUT: -// filtration value of simplex j on the line. -/** - * @brief Writes the filters of each simplex on new_filter along the a slope 1 - * line. - * - * @param line_basepoint Basepoint of a slope 1 line in \f$\mathbb R^n\f$ - * @param filter_list Multi-filtration of simplices. Format : - * [[filtration_value for simplex] for dimension] - * @param new_filter Container of the output. - * @param ignore_last Ignore this parameter. It is meant for compatibility - * with old functions. - */ - -void get_filter_from_line(const point_type &lineBasepoint, - const std::vector &filterList, - filtration_type &newFilter, - const Box &box, bool ignoreLast) { - // if (verbose && Debug::debug) { - // Debug::disp_vect(lineBasepoint); - // } - - unsigned int dimension = lineBasepoint.size() + 1 - ignoreLast; - - // value_type minLength = box.get_lower_corner().back(); - // value_type maxLength = box.get_upper_corner().back(); - // // #pragma omp parallel for reduction(max : minLength) - // for (unsigned int i = 0; i &box, - const point_type &basepoint) { - Gudhi::multi_filtration::One_critical_filtration - point_(point); - // if (is_smaller(point, box.get_upper_corner())) return; - if (point_ <= box.get_upper_corner()) - return; - - // if (verbose && Debug::debug) Debug::disp_vect(point); - - if (basepoint[0] == negInf) - return; - - // ie. point at infinity, assumes [basepoint,0] is smaller than box.second - if (point.back() == inf) { - // if (verbose) std::cout << " Infinite point" << std::endl; - - value_type threshold = box.get_upper_corner().back(); - for (unsigned int i = 0; i < point.size(); i++) { - threshold = std::min(threshold, box.get_upper_corner()[i] - basepoint[i]); - } - for (unsigned int i = 0; i < point.size() - 1; i++) - point[i] = basepoint[i] + threshold; - point.back() = threshold; - - return; - } - - // if (!is_greater(point, box.get_lower_corner())) { - if (box.get_lower_corner() <= point_) { - point[0] = inf; // puts point to infinity - // if (verbose) std::cout << "buggy point" << std::endl; - return; - } - // in this last case, at least 1 coord of point is is_greater than a coord of - // box.second - - value_type threshold = point[0] - box.get_upper_corner()[0]; - for (std::size_t i = 1; i < point.size(); i++) { - threshold = std::max(threshold, point[i] - box.get_upper_corner()[i]); - } - - // if (verbose) - // std::cout << "Thresholding the point with "<< threshold << " at - // "; - - for (std::size_t i = 0; i < point.size(); i++) - point[i] -= threshold; - - // if (verbose && Debug::debug) Debug::disp_vect(point); -} - -/** - * @brief Threshold a point to the positive cone of b=box.first - * (ie. the set \f$\{x \in \mathbb R^n \mid x \ge b\}) - * along the slope 1 line crossing this point. - * - * @param point The point to threshold. - * @param box box.fist is the point defining where to threshold. - * @param basepoint Basepoint of the slope 1 line crossing the point. - * Meant to handle infinite cases (when the point have infinite coordinates, - * we cannot infer the line). - */ - -void threshold_down(point_type &point, const Box &box, - const point_type &basepoint) { - if (basepoint[0] == negInf) - return; - - if (point.back() == inf) { // ie. point at infinity -> feature never appearing - return; - } - - // if (is_greater(point, box.get_lower_corner())) return; - if (point >= box.get_lower_corner()) - return; - - // if (!is_smaller(point, box.get_upper_corner())) { - if (!(point <= box.get_upper_corner())) { - point[0] = inf; // puts point to infinity - return; - } - - value_type threshold = box.get_lower_corner()[0] - point[0]; - for (unsigned int i = 1; i < point.size(); i++) { - threshold = std::max(threshold, box.get_lower_corner()[i] - point[i]); - } - for (unsigned int i = 0; i < point.size(); i++) - point[i] += threshold; -} - -} // namespace Gudhi::multiparameter::mma - -#endif // VINEYARDS_TRAJECTORIES_H_INCLUDED diff --git a/multipers/pickle.py b/multipers/pickle.py index 2bf34fda..844365af 100644 --- a/multipers/pickle.py +++ b/multipers/pickle.py @@ -87,4 +87,3 @@ def load(path: str): return load_with_axis(sms) case _: raise Exception("Invalid Signed Measure !") - diff --git a/multipers/plots.py b/multipers/plots.py index 3ed8184b..ed5fac7d 100644 --- a/multipers/plots.py +++ b/multipers/plots.py @@ -81,8 +81,7 @@ def _plot_signed_measure_4( **plt_kwargs, # ignored ftm ): # compute the maximal rectangle area - pts = np.clip(pts, a_min=-np.inf, - a_max=np.array((*threshold, *threshold))[None, :]) + pts = np.clip(pts, a_min=-np.inf, a_max=np.array((*threshold, *threshold))[None, :]) alpha_rescaling = 0 for rectangle, weight in zip(pts, weights): if rectangle[2] >= x_smoothing * rectangle[0]: @@ -171,9 +170,9 @@ def plot_signed_measures(signed_measures, threshold=None, size=4, alpha=None): nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size) ) for ax, signed_measure in zip(axes, signed_measures): - plot_signed_measure(signed_measure=signed_measure, - ax=ax, threshold=threshold, - alpha=alpha) + plot_signed_measure( + signed_measure=signed_measure, ax=ax, threshold=threshold, alpha=alpha + ) plt.tight_layout() @@ -189,6 +188,7 @@ def plot_surface( **plt_args, ): import matplotlib + grid = [to_numpy(g) for g in grid] hf = to_numpy(hf) if ax is None: @@ -206,15 +206,16 @@ def plot_surface( cmap = _cmap if discrete_surface or not contour: # for shading="flat" - grid = [np.concatenate([g, [g[-1]*1.1 - .1*g[0]]]) for g in grid] + grid = [np.concatenate([g, [g[-1] * 1.1 - 0.1 * g[0]]]) for g in grid] if discrete_surface: if has_negative_values: bounds = np.arange(-5, 6, 1, dtype=int) else: bounds = np.arange(0, 11, 1, dtype=int) norm = matplotlib.colors.BoundaryNorm(bounds, cmap.N, extend="max") - im = ax.pcolormesh(grid[0], grid[1], hf.T, cmap=cmap, - norm=norm, shading="flat", **plt_args) + im = ax.pcolormesh( + grid[0], grid[1], hf.T, cmap=cmap, norm=norm, shading="flat", **plt_args + ) cbar = fig.colorbar( matplotlib.cm.ScalarMappable(cmap=cmap, norm=norm), spacing="proportional", @@ -225,19 +226,19 @@ def plot_surface( if contour: levels = plt_args.pop("levels", 50) - im = ax.contourf(grid[0], grid[1], hf.T, - cmap=cmap, levels=levels, **plt_args) + im = ax.contourf(grid[0], grid[1], hf.T, cmap=cmap, levels=levels, **plt_args) else: - im = ax.pcolormesh(grid[0], grid[1], hf.T, - cmap=cmap, shading="flat", **plt_args) + im = ax.pcolormesh( + grid[0], grid[1], hf.T, cmap=cmap, shading="flat", **plt_args + ) return im def plot_surfaces(HF, size=4, **plt_args): grid, hf = HF - assert ( - hf.ndim == 3 - ), f"Found hf.shape = {hf.shape}, expected ndim = 3 : degree, 2-parameter surface." + assert hf.ndim == 3, ( + f"Found hf.shape = {hf.shape}, expected ndim = 3 : degree, 2-parameter surface." + ) num_degrees = hf.shape[0] fig, axes = plt.subplots( nrows=1, ncols=num_degrees, figsize=(num_degrees * size, size) @@ -281,6 +282,9 @@ def plot2d_PyModule( xlabel=None, ylabel=None, cmap=None, + outline_width=0.1, + outline_threshold=np.inf, + interleavings=None, ): import matplotlib @@ -309,7 +313,7 @@ def plot2d_PyModule( ax.set(xlim=[box[0][0], box[1][0]], ylim=[box[0][1], box[1][1]]) n_summands = len(corners) for i in range(n_summands): - trivial_summand = True + summand_interleaving = 0 if interleavings is None else interleavings[i] list_of_rect = [] for birth in corners[i][0]: if len(birth) == 1: @@ -320,34 +324,50 @@ def plot2d_PyModule( death = np.asarray([death[0]] * 2) death = np.asarray(death).clip(max=box[1]) if death[1] > birth[1] and death[0] > birth[0]: - if trivial_summand and _d_inf(birth, death) > min_persistence: - trivial_summand = False + if interleavings is None: + summand_interleaving = max( + _d_inf(birth, death), summand_interleaving + ) if shapely: list_of_rect.append( - _rectangle_box( - birth[0], birth[1], death[0], death[1]) + _rectangle_box(birth[0], birth[1], death[0], death[1]) ) else: list_of_rect.append( - _rectangle(birth, death, cmap( - i / n_summands), alpha) + _rectangle(birth, death, cmap(i / n_summands), alpha) ) - if not (trivial_summand): + if summand_interleaving > min_persistence: + outline_summand = ( + "black" if (summand_interleaving > outline_threshold) else None + ) if separated: fig, ax = plt.subplots() - ax.set(xlim=[box[0][0], box[1][0]], - ylim=[box[0][1], box[1][1]]) + ax.set(xlim=[box[0][0], box[1][0]], ylim=[box[0][1], box[1][1]]) if shapely: summand_shape = union_all(list_of_rect) if type(summand_shape) is _Polygon: xs, ys = summand_shape.exterior.xy - ax.fill(xs, ys, alpha=alpha, fc=cmap( - i / n_summands), ec="None") + ax.fill( + xs, + ys, + alpha=alpha, + fc=cmap(i / n_summands), + ec=outline_summand, + lw=outline_width, + ls="-", + ) else: for polygon in summand_shape.geoms: xs, ys = polygon.exterior.xy - ax.fill(xs, ys, alpha=alpha, fc=cmap( - i / n_summands), ec="None") + ax.fill( + xs, + ys, + alpha=alpha, + fc=cmap(i / n_summands), + ec=outline_summand, + lw=outline_width, + ls="-", + ) else: for rectangle in list_of_rect: ax.add_patch(rectangle) @@ -411,8 +431,7 @@ def color(d): if len(s) == 2: # simplexe = segment xx = np.array([pts[a, 0] for a in s]) yy = np.array([pts[a, 1] for a in s]) - plt.plot(xx, yy, c=color(density), alpha=1, - zorder=10 * density, lw=1.5) + plt.plot(xx, yy, c=color(density), alpha=1, zorder=10 * density, lw=1.5) if len(s) == 3: # simplexe = triangle xx = np.array([pts[a, 0] for a in s]) yy = np.array([pts[a, 1] for a in s]) diff --git a/multipers/point_measure.pyx b/multipers/point_measure.pyx index 61ca2544..94a4f604 100644 --- a/multipers/point_measure.pyx +++ b/multipers/point_measure.pyx @@ -11,7 +11,7 @@ from collections import defaultdict cnp.import_array() from scipy import sparse -from multipers.array_api import api_from_tensor +from multipers.array_api import api_from_tensor, api_from_tensors import multipers.grids as mpg @@ -240,6 +240,18 @@ def zero_out_sms(sms, mass_default): Zeros out the modules outside of \f$ \{ x\in \mathbb R^n \mid x \le \mathrm{mass_default}\}\f$. """ return tuple(zero_out_sm(pts,weights, mass_default) for pts,weights in sms) +def add_sms(sms): + if len(sms) == 0: + return (np.empty((0,2)), np.empty()) + pts = tuple(sm[0][:] for sm in sms) + api = api_from_tensor(pts[0]) + pts = api.cat(pts) + + weights = tuple(sm[1][:] for sm in sms) + api = api_from_tensor(weights[0]) + weights = api.cat(weights) + + return (pts,weights) @cython.boundscheck(False) @cython.wraparound(False) diff --git a/multipers/simplex_tree_multi.pxd b/multipers/simplex_tree_multi.pxd index 5111fec9..230dd436 100644 --- a/multipers/simplex_tree_multi.pxd +++ b/multipers/simplex_tree_multi.pxd @@ -72,6 +72,7 @@ cdef extern from "Simplex_tree_multi_interface.h" namespace "Gudhi::multiparamet dimension_type upper_bound_dimension() nogil bool find_simplex(vector[int]& simplex) nogil bool insert(vector[int]& simplex, F& filtration) noexcept nogil + bool insert_force(vector[int]& simplex, F& filtration) noexcept nogil # vector[simplex_filtration_type] get_star(const vector[int]& simplex) nogil # vector[simplex_filtration_type] get_cofaces(const vector[int]& simplex, int dimension) nogil void expansion(int max_dim) except + nogil @@ -100,20 +101,21 @@ cdef extern from "Simplex_tree_multi_interface.h" namespace "Gudhi::multiparamet void set_keys_to_enumerate() nogil const int get_key(const simplex_type) nogil void set_key(simplex_type, int) nogil - void fill_lowerstar(const F&, int) except+ nogil + void fill_lowerstar(vector[value_type]&, int) except+ nogil simplex_list get_simplices_of_dimension(int) nogil edge_list get_edge_list() nogil # euler_char_list euler_char(const vector[filtration_type]&) nogil void resize_all_filtrations(int) nogil - void set_number_of_parameters(int) nogil - int get_number_of_parameters() nogil + void set_num_parameters(int) nogil + int num_parameters() nogil void serialize(char* buffer, const size_t buffer_size) except + nogil void deserialize(const char* buffer, const size_t buffer_size) except + nogil size_t get_serialization_size() nogil void clear() nogil - - - void from_std(char*, size_t, int, F&) nogil + + dimension_type simplex_dimension(const simplex_type&) nogil + + void from_std(char*, size_t, int, vector[value_type]&) nogil void to_std(intptr_t, Line[double],int ) nogil void to_std_linear_projection(intptr_t, vector[double]) nogil void squeeze_filtration_inplace(vector[vector[double]] &, bool) nogil diff --git a/multipers/simplex_tree_multi.pyx.tp b/multipers/simplex_tree_multi.pyx.tp index bd90c78c..0aade249 100644 --- a/multipers/simplex_tree_multi.pyx.tp +++ b/multipers/simplex_tree_multi.pyx.tp @@ -9,8 +9,9 @@ with open("build/tmp/_simplextrees_.pkl", "rb") as f: to_iter = pickle.load(f) st_map = {} -for CTYPE,PYTYPE, SHORT,Filtration, is_kcritical, FSHORT in to_iter: - st_map[PYTYPE, is_kcritical] = CTYPE +for D in to_iter: + locals().update(D) + st_map[PY_VALUE_TYPE, IS_KCRITICAL] = PY_CLASS_NAME }} @@ -55,6 +56,7 @@ ctypedef fused some_float: ctypedef vector[pair[pair[int,int],pair[float,float]]] edge_list_type from typing import Any, Union, ClassVar +from collections.abc import Sequence cimport numpy as cnp import numpy as np @@ -76,18 +78,18 @@ SAFE_CONVERSION=False #Slower but at least it works everywhere _available_strategies =Lstrategies +global available_dtype +available_dtype = set([ +{{for D in to_iter}} + {{D['PY_VALUE_TYPE']}}, +{{endfor}} +]) -{{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - - - -ctypedef {{Filtration}} {{FSHORT}} - - - +{{for D in to_iter}} +# WARN : updating locals {{locals().update(D)}} # SimplexTree python interface -cdef class SimplexTreeMulti_{{FSHORT}}: +cdef class {{D["PY_CLASS_NAME"]}}: """The simplex tree is an efficient and flexible data structure for representing general (filtered) simplicial complexes. The data structure is described in Jean-Daniel Boissonnat and Clément Maria. The Simplex @@ -102,8 +104,8 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef public object filtration_grid cdef public bool _is_function_simplextree # TODO : deprecate # Get the pointer casted as it should be - cdef Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]* get_ptr(self) noexcept nogil: - return (self.thisptr) + cdef {{ST_INTERFACE}}* get_ptr(self) noexcept nogil: + return <{{ST_INTERFACE}}*>(self.thisptr) # cdef Simplex_tree_persistence_interface * pcohptr # Fake constructor that does nothing but documenting the constructor @@ -124,48 +126,46 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @staticmethod cdef {{CTYPE}} T_minus_inf(): - {{if SHORT[0] == 'f'}} + {{if IS_FLOAT}} return <{{CTYPE}}>(-np.inf) {{else}} return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).min) {{endif}} @staticmethod cdef {{CTYPE}} T_inf(): - {{if SHORT[0] == 'f'}} + {{if IS_FLOAT}} return <{{CTYPE}}>(np.inf) {{else}} return <{{CTYPE}}>(np.iinfo({{PYTYPE}}).max) {{endif}} @property def is_kcritical(self)->bool: - return {{is_kcritical}} - # The real cython constructor + return {{IS_KCRITICAL}} + @staticmethod + def _isinstance(other)->bool: + return isinstance(other, {{PY_CLASS_NAME}}) def __cinit__(self, other = None, int num_parameters=-1, - default_values=np.asarray([SimplexTreeMulti_{{FSHORT}}.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ? + default_values=np.asarray([{{D["PY_CLASS_NAME"]}}.T_minus_inf()]), # I'm not sure why `[]` does not work. Cython bug ? bool safe_conversion=False, ): #TODO doc - {{if is_kcritical}} - cdef {{FSHORT}} c_default_values = _py2kc_{{SHORT}}(np.asarray([default_values], dtype= {{PYTYPE}})) - {{else}} - cdef {{FSHORT}} c_default_values = _py21c_{{SHORT}}(np.asarray(default_values, dtype={{PYTYPE}})) - {{endif}} + cdef vector[{{CTYPE}}] c_default_values cdef intptr_t other_ptr cdef char[:] buffer cdef size_t buffer_size cdef char* buffer_start if other is not None: - if isinstance(other, SimplexTreeMulti_{{FSHORT}}): + if isinstance(other, {{D["PY_CLASS_NAME"]}}): other_ptr = other.thisptr - self.thisptr = (new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}](dereference(other_ptr))) ## prevents calling destructor of other + self.thisptr = (new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}](dereference(other_ptr))) ## prevents calling destructor of other if num_parameters <=0: num_parameters = other.num_parameters self.filtration_grid = other.filtration_grid elif isinstance(other, SimplexTree): # Constructs a SimplexTreeMulti from a SimplexTree - self.thisptr = (new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]()) + self.thisptr = (new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}]()) if num_parameters <= 0: num_parameters = 1 if safe_conversion or SAFE_CONVERSION: - new_st_multi = _safe_simplextree_multify_{{FSHORT}}(other, num_parameters = num_parameters, default_values=np.asarray(default_values)) + new_st_multi = _safe_simplextree_multify_{{PyFil}}(other, num_parameters = num_parameters, default_values=np.asarray(default_values)) self.thisptr, new_st_multi.thisptr = new_st_multi.thisptr, self.thisptr else: stree_buffer = other.__getstate__() @@ -173,20 +173,44 @@ cdef class SimplexTreeMulti_{{FSHORT}}: buffer_size = buffer.shape[0] buffer_start = &buffer[0] + assert num_parameters > 0 + if isinstance(default_values, Sequence): + # tedious way to do a resize + if len(default_values) > num_parameters: + del default_values[num_parameters:] + elif len(default_values) < num_parameters: + padding = [{{PY_CLASS_NAME}}.T_minus_inf()] * (num_parameters - len(default_values)) + # padding is added at the beginning for the case someone just omitted index 0 which gets + # replaced by the original value. + default_values = padding + default_values + assert len(default_values) == num_parameters + else: + assert default_values.ndim == 1 + # tedious way to do a resize + if default_values.shape[0] > num_parameters: + default_values = default_values[:num_parameters] + elif default_values.shape[0] < num_parameters: + padding = np.full(num_parameters - default_values.shape[0], {{PY_CLASS_NAME}}.T_minus_inf(), dtype=default_values.dtype) + # padding is added at the beginning for the case someone just omitted index 0 which gets + # replaced by the original value. + default_values = np.concatenate((padding, default_values)) + assert default_values.shape[0] == num_parameters + c_default_values = np.asarray(default_values, dtype={{PYTYPE}}) + with nogil: - self.get_ptr().from_std(buffer_start, buffer_size, num_parameters, c_default_values) + self.get_ptr().from_std(buffer_start, buffer_size, 0, c_default_values) else: raise TypeError("`other` argument requires to be of type `SimplexTree`, `SimplexTreeMulti`, or `None`.") else: if num_parameters <=0: num_parameters = 2 # I don't know how dangerous this is, but this is mostly used. - self.thisptr = (new Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]()) + self.thisptr = (new Simplex_tree_multi_interface[{{PyFil}}, {{CTYPE}}]()) self.set_num_parameter(num_parameters) self._is_function_simplextree = False self.filtration_grid=[] def __dealloc__(self): - cdef Simplex_tree_multi_interface[{{FSHORT}},{{CTYPE}}]* ptr = self.get_ptr() + cdef Simplex_tree_multi_interface[{{PyFil}},{{CTYPE}}]* ptr = self.get_ptr() if ptr != NULL: del ptr # TODO : is that enough ?? @@ -209,6 +233,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: self.get_ptr().serialize(buffer_start, buffer_size) return np_buffer + def __setstate__(self, state): """Construct the SimplexTree data structure from a Numpy Array (cf. :func:`~gudhi.SimplexTree.__getstate__`) in order to unpickle a SimplexTree. @@ -224,9 +249,23 @@ cdef class SimplexTreeMulti_{{FSHORT}}: self.get_ptr().clear() # New pointer is a deserialized simplex tree self.get_ptr().deserialize(buffer_start, buffer_size) + #temporary: deserialize should also update the number of parameters, but to keep compatibility with + #current gudhi simplex tree, we have to externalize it for now + cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() + cdef pair[vector[int],{{PyFil}}*] p + if it != end: + sh = dereference(it) + p = self.get_ptr().get_simplex_and_filtration(sh) + self.get_ptr().set_num_parameters((p.second)[0].num_parameters()) + else: + #not ideal, but hopefully sufficient for now + #will be corrected once the gudhi simplex tree can dump the number of parameters + self.get_ptr().set_num_parameters(0) - def copy(self)->SimplexTreeMulti_{{FSHORT}}: + def copy(self)->{{PY_CLASS_NAME}}: """ :returns: A simplex tree that is a deep copy of itself. :rtype: SimplexTreeMulti @@ -234,7 +273,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :note: The persistence information is not copied. If you need it in the clone, you have to call :func:`compute_persistence` on it even if you had already computed it in the original. """ - stree = SimplexTreeMulti_{{FSHORT}}(self,num_parameters=self.num_parameters) + stree = {{PY_CLASS_NAME}}(self,num_parameters=self.num_parameters) stree.filtration_grid = self.filtration_grid stree._is_function_simplextree = self._is_function_simplextree return stree @@ -252,7 +291,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: """ return self[simplex] - def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->SimplexTreeMulti_{{FSHORT}}: + def assign_filtration(self, simplex:list|np.ndarray, filtration:list|np.ndarray)->{{PY_CLASS_NAME}}: """This function assigns a new multi-critical filtration value to a given N-simplex. @@ -270,24 +309,25 @@ cdef class SimplexTreeMulti_{{FSHORT}}: any function that relies on the filtration property, like :meth:`persistence`. """ - assert len(filtration)>0 and len(filtration) % self.get_ptr().get_number_of_parameters() == 0 - # self.get_ptr().assign_simplex_filtration(simplex, {{FSHORT}}(filtration)) - {{if is_kcritical}} + assert len(filtration)>0 and len(filtration) % self.get_ptr().num_parameters() == 0 + # self.get_ptr().assign_simplex_filtration(simplex, {{PyFil}}(filtration)) + # filtration = np.asarray(filtration, dtype={{PYTYPE}})[None,:] + # self.get_ptr().assign_simplex_filtration(simplex, _py2kc_{{SHORT_VALUE_TYPE}}(filtration)) + {{if IS_KCRITICAL}} filtration = np.asarray(filtration, dtype={{PYTYPE}})[None,:] - self.get_ptr().assign_simplex_filtration(simplex, _py2kc_{{SHORT}}(filtration)) {{else}} filtration = np.asarray(filtration, dtype={{PYTYPE}}) - self.get_ptr().assign_simplex_filtration(simplex, _py21c_{{SHORT}}(filtration)) {{endif}} + self.get_ptr().assign_simplex_filtration(simplex, {{P2C_Fil}}(filtration)) return self def __getitem__(self, simplex)->np.ndarray: cdef vector[int] csimplex = simplex - cdef {{FSHORT}}* f_ptr = self.get_ptr().simplex_filtration(csimplex) - {{if is_kcritical}} - return _ff2kcview_{{SHORT}}(f_ptr) + cdef {{PyFil}}* f_ptr = self.get_ptr().simplex_filtration(csimplex) + {{if IS_KCRITICAL}} + return {{C2P_Fil}}(f_ptr) {{else}} - return _ff21cview_{{SHORT}}(f_ptr) + return {{C2P_Fil}}(f_ptr) {{endif}} @@ -328,6 +368,13 @@ cdef class SimplexTreeMulti_{{FSHORT}}: methods). """ return self.get_ptr().dimension() + def simplex_dimension(self, vector[int] simplex)->int: + """This function returns the dimension of the simplex. + + :returns: simplex dimension. + :rtype: int + """ + return self.get_ptr().simplex_dimension(simplex) def upper_bound_dimension(self)->int: """This function returns a valid dimension upper bound of the simplicial complex. @@ -348,19 +395,21 @@ cdef class SimplexTreeMulti_{{FSHORT}}: simplices of dimension > `dim` by lower-star values. """ - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() - cdef One_critical_filtration[{{CTYPE}}] minf = One_critical_filtration[{{CTYPE}}](self.get_ptr().get_number_of_parameters()) + # TODO: there is probably a better way to get the inf value... + cdef {{OneCriticalFil}} tmp = {{OneCriticalFil}}(self.get_ptr().num_parameters()) + cdef vector[{{CTYPE}}] minf = vector[{{CTYPE}}](self.get_ptr().num_parameters(), tmp(0,0)) cdef int simplex_dimension with nogil: while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) simplex_dimension = (pair_sf.first.size())-1 if simplex_dimensionSimplexTreeMulti_{{FSHORT}} : + def insert_batch(self, vertex_array, filtrations)->{{PY_CLASS_NAME}}: """Inserts k-simplices given by a sparse array in a format similar to `torch.sparse `_. The n-th simplex has vertices `vertex_array[0,n]`, ..., @@ -468,7 +517,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef int[:,:] vertex_array_ = np.asarray(vertex_array, dtype=np.int32) cdef Py_ssize_t k = vertex_array_.shape[0] cdef Py_ssize_t n = vertex_array_.shape[1] - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef int num_parameters = self.get_ptr().num_parameters() cdef bool empty_filtration = (filtrations.size == 0) cdef {{CTYPE}}[:,:] F_view = np.asarray(filtrations, dtype = {{PYTYPE}}) @@ -480,9 +529,9 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - cdef {{Filtration}} w + cdef {{PyFil}} w if empty_filtration: - w = One_critical_filtration[{{CTYPE}}](num_parameters) # at -inf by default + w = {{PyFil}}(num_parameters) # at -inf by default with nogil: for i in range(n): # vertex @@ -492,7 +541,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: if not empty_filtration: # for j in range(num_parameters): # w.push_back(<{{CTYPE}}>filtrations[i,j]) - w = _py21c_{{SHORT}}(F_view[i,:]) + w = _py21c_{{SHORT_VALUE_TYPE}}(F_view[i,:]) self.get_ptr().insert(v, w) v.clear() #repair filtration if necessary @@ -507,43 +556,48 @@ cdef class SimplexTreeMulti_{{FSHORT}}: """ cdef Py_ssize_t num_vertices = nodes_filtrations.shape[0] cdef Py_ssize_t num_parameters = nodes_filtrations.shape[1] - assert self.get_ptr().get_number_of_parameters() == num_parameters and self.num_vertices == num_vertices, f"Invalid shape {nodes_filtrations.shape}. Should be (?,{self.num_parameters=})." + assert self.get_ptr().num_parameters() == num_parameters and self.num_vertices == num_vertices, f"Invalid shape {nodes_filtrations.shape}. Should be (?,{self.num_parameters=})." - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() cdef Py_ssize_t node_idx = 0 cdef {{CTYPE}}[:,:] F = nodes_filtrations cdef {{CTYPE}} minus_inf = -np.inf + cdef {{CTYPE}}* f with nogil: while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) if pair_sf.first.size() == 1: # dimension == 0 - {{if is_kcritical}} + {{if IS_KCRITICAL}} for i in range(pair_sf.second.size()): for j in range(num_parameters): dereference(pair_sf.second)[i][j] = F[node_idx,j] {{else}} for i in range(num_parameters): - dereference(pair_sf.second)[i] = F[node_idx,i] + # Weird cython bug with "Cannot assign to or delete this" otherwise + f = &(dereference(pair_sf.second)(0,i)) + f[0] = F[node_idx,i] {{endif}} node_idx += 1 # with gil: # print(pair_sf.first, node_idx,i, F[node_idx,i]) else: - {{if is_kcritical}} + {{if IS_KCRITICAL}} for i in range(pair_sf.second.size()): for j in range(num_parameters): - dereference(pair_sf.second)[i][j] = minus_inf + f = &(dereference(pair_sf.second)(i,j)) + f[0] = minus_inf {{else}} for i in range(num_parameters): - dereference(pair_sf.second)[i] = minus_inf + f = &(dereference(pair_sf.second)(0,i)) + f[0] = minus_inf {{endif}} preincrement(it) self.make_filtration_non_decreasing() return self - def assign_all(self, filtration_values)-> SimplexTreeMulti_{{FSHORT}}: + def assign_all(self, filtration_values)-> {{PY_CLASS_NAME}}: """ Updates the filtration values of all of the simplices, with `filtration_values` with order given by the simplextree iterator, e.g. self.get_simplices(). @@ -553,18 +607,21 @@ cdef class SimplexTreeMulti_{{FSHORT}}: assert num_simplices == self.num_simplices, f"Number of filtration values {filtration_values.shape[0]} is not the number of simplices {self.num_simplices}" assert num_parameters == self.num_parameters, f"Number of parameter do not coincide {filtration_values.shape[1]} vs {self.num_parameters}" - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() - cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it) + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it) cdef int counter =0 # cdef cnp.ndarray[{{CTYPE}},ndim=1] current_filtration cdef {{CTYPE}}[:,:] F = filtration_values + cdef {{CTYPE}}* f with nogil: while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) for i in range(num_parameters): - dereference(pair_sf.second)[i] = F[counter,i] + # Weird cython bug with "Cannot assign to or delete this" otherwise + f = &(dereference(pair_sf.second)(0,i)) + f[0] = F[counter,i] # current_filtration= F[counter] counter += 1 # yield SimplexTreeMulti._pair_simplex_filtration_to_python(out) @@ -574,7 +631,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @cython.boundscheck(False) @cython.wraparound(False) - def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->SimplexTreeMulti_{{FSHORT}}: + def assign_batch_filtration(self, some_int[:,:] vertex_array, some_float[:,:] filtrations, bool propagate=True)->{{PY_CLASS_NAME}}: """Assign k-simplices given by a sparse array in a format similar to `torch.sparse `_. The n-th simplex has vertices `vertex_array[0,n]`, ..., @@ -594,7 +651,8 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - cdef One_critical_filtration[{{CTYPE}}] w + cdef vector[{{CTYPE}}] w + cdef {{PyFil}} f cdef int n_parameters = self.num_parameters with nogil: for i in range(n): @@ -602,7 +660,8 @@ cdef class SimplexTreeMulti_{{FSHORT}}: v.push_back(vertex_array[j, i]) for j in range(n_parameters): w.push_back(<{{CTYPE}}>filtrations[i,j]) - self.get_ptr().assign_simplex_filtration(v, w) + f = {{PyFil}}(w.begin(), w.end(), n_parameters) + self.get_ptr().assign_simplex_filtration(v, f) v.clear() w.clear() if propagate: self.make_filtration_non_decreasing() @@ -615,16 +674,16 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :returns: The simplices. :rtype: generator with tuples(simplex, filtration) """ - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() - cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it) - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it) + cdef int num_parameters = self.get_ptr().num_parameters() cdef dict[tuple,int] out = {} cdef int dim while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) # TODO: optimize https://stackoverflow.com/questions/16589791/most-efficient-property-to-hash-for-numpy-array - key = tuple(_ff21cview_{{SHORT}}(pair_sf.second)) + key = tuple({{C2P_Fil}}(pair_sf.second)) dim = pair_sf.first.size() -1 % 2 out[key] = out.get(key,0)+(-1)**dim num_keys = len(out) @@ -639,12 +698,15 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @cython.boundscheck(False) @cython.wraparound(False) - def insert_batch(self, vertex_array, filtrations)->SimplexTreeMulti_{{FSHORT}} : + def insert_batch(self, vertex_array, filtrations)->{{PY_CLASS_NAME}} : """Inserts k-simplices given by a sparse array in a format similar to `torch.sparse `_. The i-th simplex has vertices `vertex_array[0,i]`, ..., `vertex_array[n,i]` and j-th filtration value `filtrations[i,j,num_parameters]`. + If the given filtration is not empty, it has to represent a valid filtration even with respect to faces already + present in the simplex tree. Contrary to the 1-critical case, no automatic revision is made. + :param vertex_array: the k-simplices to insert. :type vertex_array: numpy.array of shape (k+1,n) :param filtrations: the filtration values. @@ -658,7 +720,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef Py_ssize_t k = vertex_array_.shape[0] cdef Py_ssize_t n = vertex_array_.shape[1] cdef Py_ssize_t max_crit_deg = filtrations_.shape[1] - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef int num_parameters = self.get_ptr().num_parameters() cdef bool empty_filtration = (filtrations_.size == 0) if not empty_filtration : @@ -669,10 +731,10 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef Py_ssize_t i cdef Py_ssize_t j cdef vector[int] v - cdef One_critical_filtration[{{CTYPE}}] w_temp - cdef {{Filtration}} w + cdef {{OneCriticalFil}} w_temp + cdef {{PyFil}} w if empty_filtration: - w = {{Filtration}}() # at -inf by default + w = {{PyFil}}(num_parameters) # at -inf by default with nogil: for i in range(n): v.clear() @@ -681,8 +743,8 @@ cdef class SimplexTreeMulti_{{FSHORT}}: v.push_back(vertex_array_[j, i]) #filtration if not empty_filtration: - w = _py2kc_{{SHORT}}(filtrations_[i]) - self.get_ptr().insert(v, w) + w = {{P2C_Fil}}(filtrations_[i]) + self.get_ptr().insert_force(v, w) #repair filtration if necessary if empty_filtration: @@ -698,25 +760,25 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :returns: The simplices. :rtype: generator with tuples(simplex, filtration) """ - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() - cdef Simplex_tree_multi_simplex_handle[{{FSHORT}}] sh = dereference(it) - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplex_handle[{{PyFil}}] sh = dereference(it) + cdef int num_parameters = self.get_ptr().num_parameters() while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) yield ( np.asarray(pair_sf.first, dtype=int), - {{if is_kcritical}} - _ff2kcview_{{SHORT}}(pair_sf.second) + {{if IS_KCRITICAL}} + {{C2P_Fil}}(pair_sf.second) {{else}} - _ff21cview_{{SHORT}}(pair_sf.second) + {{C2P_Fil}}(pair_sf.second) {{endif}} ) preincrement(it) def _get_raw_filtration(self): - {{if is_kcritical}} + {{if IS_KCRITICAL}} raise NotImplementedError("Not implemented for multicritical filtrations") {{else}} if self.is_squeezed: @@ -724,15 +786,15 @@ cdef class SimplexTreeMulti_{{FSHORT}}: cdef int num_parameters = self.num_parameters cdef {{CTYPE}}[:,:] current_filtration = np.empty((self.num_simplices, self.num_parameters), dtype=self.dtype) - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] it = self.get_ptr().get_simplices_iterator_begin() - cdef Simplex_tree_multi_simplices_iterator[{{FSHORT}}] end = self.get_ptr().get_simplices_iterator_end() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] it = self.get_ptr().get_simplices_iterator_begin() + cdef Simplex_tree_multi_simplices_iterator[{{PyFil}}] end = self.get_ptr().get_simplices_iterator_end() cdef int i=0 with nogil: while it != end: - pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) + pair_sf = self.get_ptr().get_simplex_and_filtration(dereference(it)) for j in range(num_parameters): - current_filtration[i,j] = dereference(pair_sf.second)[j] + current_filtration[i,j] = dereference(pair_sf.second)(0,j) preincrement(it) i = i+1 return np.asarray(current_filtration, dtype=self.dtype) @@ -748,17 +810,17 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :returns: The (simplices of the) skeleton of a maximum dimension. :rtype: generator with tuples(simplex, filtration) """ - cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] it = self.get_ptr().get_skeleton_iterator_begin(dimension) - cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] end = self.get_ptr().get_skeleton_iterator_end(dimension) - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] it = self.get_ptr().get_skeleton_iterator_begin(dimension) + cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] end = self.get_ptr().get_skeleton_iterator_end(dimension) + cdef int num_parameters = self.get_ptr().num_parameters() while it != end: # yield self.get_ptr().get_simplex_and_filtration(dereference(it)) pair = self.get_ptr().get_simplex_and_filtration(dereference(it)) yield (np.asarray(pair.first, dtype=int), - {{if is_kcritical}} - _ff2kcview_{{SHORT}}(pair.second) + {{if IS_KCRITICAL}} + {{C2P_Fil}}(pair.second) {{else}} - _ff21cview_{{SHORT}}(pair.second) + {{C2P_Fil}}(pair.second) {{endif}} ) preincrement(it) @@ -822,21 +884,21 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :returns: The (simplices of the) boundary of a simplex :rtype: generator with tuples(simplex, filtration) """ - cdef pair[Simplex_tree_multi_boundary_iterator[{{FSHORT}}], Simplex_tree_multi_boundary_iterator[{{FSHORT}}]] it = self.get_ptr().get_boundary_iterators(simplex) + cdef pair[Simplex_tree_multi_boundary_iterator[{{PyFil}}], Simplex_tree_multi_boundary_iterator[{{PyFil}}]] it = self.get_ptr().get_boundary_iterators(simplex) - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef int num_parameters = self.get_ptr().num_parameters() while it.first != it.second: # yield self.get_ptr().get_simplex_and_filtration(dereference(it)) pair = self.get_ptr().get_simplex_and_filtration(dereference(it.first)) yield (np.asarray(pair.first, dtype=int), - {{if is_kcritical}} - _ff2kcview_{{SHORT}}(pair.second) + {{if IS_KCRITICAL}} + {{C2P_Fil}}(pair.second) {{else}} - _ff21cview_{{SHORT}}(pair.second) + {{C2P_Fil}}(pair.second) {{endif}} ) preincrement(it.first) - def remove_maximal_simplex(self, simplex)->SimplexTreeMulti_{{FSHORT}}: + def remove_maximal_simplex(self, simplex)->{{PY_CLASS_NAME}}: """This function removes a given maximal N-simplex from the simplicial complex. @@ -888,7 +950,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: """ return self.get_ptr().prune_above_dimension(dimension) - def expansion(self, int max_dim)->SimplexTreeMulti_{{FSHORT}}: + def expansion(self, int max_dim)->{{PY_CLASS_NAME}}: """Expands the simplex tree containing only its one skeleton until dimension max_dim. @@ -924,7 +986,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: out = self.get_ptr().make_filtration_non_decreasing() return out - def reset_filtration(self, filtration, min_dim = 0)->SimplexTreeMulti_{{FSHORT}}: + def reset_filtration(self, filtration, min_dim = 0)->{{PY_CLASS_NAME}}: """This function resets the filtration value of all the simplices of dimension at least min_dim. Resets all the simplex tree when `min_dim = 0`. `reset_filtration` may break the filtration property with `min_dim > 0`, and it is the user's responsibility to @@ -936,15 +998,15 @@ cdef class SimplexTreeMulti_{{FSHORT}}: :param min_dim: The minimal dimension. Default value is 0. :type min_dim: int. """ - {{if is_kcritical}} - cdef {{Filtration}} cfiltration = _py2kc_{{SHORT}}(np.asarray(filtration, dtype = {{PYTYPE}})[None,:]) + {{if IS_KCRITICAL}} + cdef {{PyFil}} cfiltration = _py2kc_{{SHORT_VALUE_TYPE}}(np.asarray(filtration, dtype = {{PYTYPE}})[None,:]) {{else}} - cdef {{Filtration}} cfiltration = _py21c_{{SHORT}}(np.asarray(filtration, dtype = {{PYTYPE}})) + cdef {{PyFil}} cfiltration = _py21c_{{SHORT_VALUE_TYPE}}(np.asarray(filtration, dtype = {{PYTYPE}})) {{endif}} self.get_ptr().reset_filtration(cfiltration, min_dim) return self - {{if not is_kcritical}} + {{if not IS_KCRITICAL}} def pts_to_indices(self,pts:np.ndarray, simplices_dimensions:Iterable[int]) -> tuple[np.ndarray,np.ndarray]: """ Returns the indices of the simplex tree with corresponding filtrations. @@ -998,7 +1060,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: bool full=False, bool ignore_warning=False, bool auto_clean=True, - )->SimplexTreeMulti_{{FSHORT}}: + )->{{PY_CLASS_NAME}}: """Edge collapse for 1-critical 2-parameter clique complex (see https://arxiv.org/abs/2211.05574). It uses the code from the github repository https://github.com/aj-alonso/filtration_domination . @@ -1070,7 +1132,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: The reduced SimplexTreeMulti having only these edges. """ - reduced_tree = SimplexTreeMulti_{{FSHORT}}(num_parameters=self.num_parameters) + reduced_tree = {{PY_CLASS_NAME}}(num_parameters=self.num_parameters) ## Adds vertices back, with good filtration if self.num_vertices > 0: @@ -1093,7 +1155,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @property def num_parameters(self)->int: - return self.get_ptr().get_number_of_parameters() + return self.get_ptr().num_parameters() def get_simplices_of_dimension(self, dim:int)->np.ndarray: return np.asarray(self.get_ptr().get_simplices_of_dimension(dim), dtype=int) def key(self, simplex:list|np.ndarray): @@ -1119,13 +1181,13 @@ cdef class SimplexTreeMulti_{{FSHORT}}: out = self.get_ptr().simplextree_to_ordered_bf() return np.asarray(out.first,dtype=filtration_dtype), tuple(out.second) if is_function_st: - {{if not is_kcritical}} + {{if not IS_KCRITICAL}} blocks = self.get_ptr().function_simplextree_to_scc() {{else}} raise Exception("Kcritical cannot be a function simplextree ?? TODO: Fixme") {{endif}} else: - {{if not is_kcritical}} + {{if not IS_KCRITICAL}} blocks = self.get_ptr().simplextree_to_scc() {{else}} blocks = self.get_ptr().kcritical_simplextree_to_scc() @@ -1134,7 +1196,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: if is_function_st: blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] else: - {{if not is_kcritical}} + {{if not IS_KCRITICAL}} blocks = [(np.asarray(f,dtype=filtration_dtype), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order {{else}} blocks = [(tuple(f), tuple(b)) for f,b in blocks[::-1]] ## presentation is on the other order @@ -1264,7 +1326,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: file.write(str(i) + " ") file.write("; ") for f in F: - {{if is_kcritical}} + {{if IS_KCRITICAL}} for fi in f: file.write(str(fi) + " ") {{else}} @@ -1279,7 +1341,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: def _get_filtration_values(self, vector[int] degrees, bool inf_to_nan:bool=False, bool return_raw = False)->Iterable[np.ndarray]: # cdef vector[int] c_degrees = degrees - # out = get_filtration_values_from_ptr[{{FSHORT}}](ptr, degrees) + # out = get_filtration_values_from_ptr[{{PyFil}}](ptr, degrees) cdef intptr_t ptr = self.thisptr cdef vector[vector[vector[{{CTYPE}}]]] out with nogil: @@ -1351,7 +1413,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: grid_strategy=None, bool inplace=False, **filtration_grid_kwargs - )->SimplexTreeMulti_{{FSHORT[:-3] + "i32"}} | SimplexTreeMulti_{{FSHORT}}: + )->{{COARSENNED_PY_CLASS_NAME}} | {{PY_CLASS_NAME}}: """ Fit the filtration of the simplextree to a grid. @@ -1385,18 +1447,18 @@ cdef class SimplexTreeMulti_{{FSHORT}}: if inplace or not coordinate_values: self.get_ptr().squeeze_filtration_inplace(c_filtration_grid, coordinate_values) else: - out = SimplexTreeMulti_{{FSHORT[:-3] + "i32"}}(num_parameters=self.num_parameters) + out = {{COARSENNED_PY_CLASS_NAME}}(num_parameters=self.num_parameters) self.get_ptr().squeeze_filtration(out.thisptr, c_filtration_grid) out.filtration_grid = filtration_grid return out return self - def unsqueeze(self, grid=None)->SimplexTreeMulti_{{FSHORT[:-3] + "f64"}}: + def unsqueeze(self, grid=None)->{{REAL_PY_CLASS_NAME}}: from multipers.grids import sanitize_grid grid = self.filtration_grid if grid is None else grid cdef vector[vector[double]] cgrid = sanitize_grid(grid, numpyfy=True) - new_slicer = SimplexTreeMulti_{{FSHORT[:-3] + "f64"}}() + new_slicer = {{REAL_PY_CLASS_NAME}}() new_slicer.get_ptr().unsqueeze_filtration(self.thisptr, cgrid) return new_slicer @@ -1408,6 +1470,9 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @property def dtype(self)->type: return {{PYTYPE}} + @property + def ftype(self)->type: + return "{{PyFil}}" def filtration_bounds(self, degrees:Iterable[int]|None=None, q:float|tuple=0, split_dimension:bool=False)->np.ndarray: """ Returns the filtrations bounds of the finite filtration values. @@ -1425,7 +1490,7 @@ cdef class SimplexTreeMulti_{{FSHORT}}: - def fill_lowerstar(self, F, int parameter)->SimplexTreeMulti_{{FSHORT}}: + def fill_lowerstar(self, F, int parameter)->{{PY_CLASS_NAME}}: """ Fills the `dimension`th filtration by the lower-star filtration defined by F. Parameters @@ -1441,19 +1506,12 @@ cdef class SimplexTreeMulti_{{FSHORT}}: self:SimplexTreeMulti """ - # for s, sf in self.get_simplices(): - # self.assign_filtration(s, [f if i != dimension else np.max(np.array(F)[s]) for i,f in enumerate(sf)]) - # cdef int c_parameter = parameter - {{if is_kcritical}} - cdef {{FSHORT}} c_F = _py2kc_{{SHORT}}(np.asarray(F,dtype={{PYTYPE}})[None,:]) - {{else}} - cdef {{FSHORT}} c_F = _py21c_{{SHORT}}(np.asarray(F,dtype={{PYTYPE}})) - {{endif}} + cdef vector[{{CTYPE}}] c_F = _py2p_{{SHORT_VALUE_TYPE}}(np.asarray(F,dtype={{PYTYPE}})) with nogil: self.get_ptr().fill_lowerstar(c_F, parameter) return self - def fill_distance_matrix(self, distance_matrix, int parameter, {{CTYPE}} node_value = 0)->SimplexTreeMulti_{{FSHORT}}: + def fill_distance_matrix(self, distance_matrix, int parameter, {{CTYPE}} node_value = 0)->{{PY_CLASS_NAME}}: """ Fills a specific parameter with a rips filtration with the given matrix. @@ -1473,21 +1531,18 @@ cdef class SimplexTreeMulti_{{FSHORT}}: ## Resets the filtration. should be optimizable F = np.zeros(shape = self.num_vertices, dtype={{PYTYPE}}) + node_value - {{if is_kcritical}} - cdef {{FSHORT}} c_F = _py2kc_{{SHORT}}(F[None,:]) - {{else}} - cdef {{FSHORT}} c_F = _py21c_{{SHORT}}(F) - {{endif}} + cdef vector[{{CTYPE}}] c_F = _py2p_{{SHORT_VALUE_TYPE}}(F) with nogil: self.get_ptr().fill_lowerstar(c_F, parameter) ## Fills the matrix in the 1-skeleton - cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] it = self.get_ptr().get_skeleton_iterator_begin(1) - cdef Simplex_tree_multi_skeleton_iterator[{{FSHORT}}] end = self.get_ptr().get_skeleton_iterator_end(1) - cdef int num_parameters = self.get_ptr().get_number_of_parameters() + cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] it = self.get_ptr().get_skeleton_iterator_begin(1) + cdef Simplex_tree_multi_skeleton_iterator[{{PyFil}}] end = self.get_ptr().get_skeleton_iterator_end(1) + cdef int num_parameters = self.get_ptr().num_parameters() cdef int num_generators cdef int N = c_distance_matrix.shape[0] + cdef {{CTYPE}}* f with nogil: while it != end: pair = self.get_ptr().get_simplex_and_filtration(dereference(it)) @@ -1497,12 +1552,15 @@ cdef class SimplexTreeMulti_{{FSHORT}}: assert iSimplexTreeMulti_{{FSHORT}}: +# def _simplextree_multify_{{PyFil}}(simplextree:SimplexTree, int num_parameters, default_values=[])->{{PY_CLASS_NAME}}: # """Converts a gudhi simplextree to a multi simplextree. # Parameters # ---------- @@ -1629,14 +1687,14 @@ cdef class SimplexTreeMulti_{{FSHORT}}: # A multi simplextree, with first filtration value being the one from the original simplextree. # """ -# if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +# if isinstance(simplextree, {{PY_CLASS_NAME}}): # return simplextree -# st = SimplexTreeMulti_{{FSHORT}}(num_parameters=num_parameters) +# st = {{PY_CLASS_NAME}}(num_parameters=num_parameters) # cdef intptr_t old_ptr = simplextree.thisptr -# {{if is_kcritical}} -# cdef {{FSHORT}} c_default_values= _py2kc_{{SHORT}}([default_values]) +# {{if IS_KCRITICAL}} +# cdef {{PyFil}} c_default_values= _py2kc_{{SHORT_VALUE_TYPE}}([default_values]) # {{else}} -# cdef {{FSHORT}} c_default_values= _py21c_{{SHORT}}([default_values]) +# cdef {{PyFil}} c_default_values= _py21c_{{SHORT_VALUE_TYPE}}([default_values]) # {{endif}} # with nogil: # st.get_ptr().from_std(old_ptr, num_parameters, c_default_values) @@ -1644,12 +1702,12 @@ cdef class SimplexTreeMulti_{{FSHORT}}: @cython.boundscheck(False) @cython.wraparound(False) -def _safe_simplextree_multify_{{FSHORT}}(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_{{FSHORT}}: - if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +def _safe_simplextree_multify_{{PyFil}}(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->{{PY_CLASS_NAME}}: + if isinstance(simplextree, {{PY_CLASS_NAME}}): return simplextree simplices = [[] for _ in range(simplextree.dimension()+1)] filtration_values = [[] for _ in range(simplextree.dimension()+1)] - st_multi = SimplexTreeMulti_{{FSHORT}}(num_parameters=1) + st_multi = {{PY_CLASS_NAME}}(num_parameters=1) if num_parameters > 1: st_multi.set_num_parameter(num_parameters) if default_values.squeeze().ndim == 0: @@ -1667,10 +1725,10 @@ def _safe_simplextree_multify_{{FSHORT}}(simplextree:SimplexTree,int num_paramet @cython.boundscheck(False) @cython.wraparound(False) -def _safe_simplextree_multify_{{FSHORT}}2(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->SimplexTreeMulti_{{FSHORT}}: - if isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +def _safe_simplextree_multify_{{PyFil}}2(simplextree:SimplexTree,int num_parameters=2, cnp.ndarray default_values=np.array(-np.inf))->{{PY_CLASS_NAME}}: + if isinstance(simplextree, {{PY_CLASS_NAME}}): return simplextree - st_multi = SimplexTreeMulti_{{FSHORT}}(num_parameters=num_parameters) + st_multi = {{PY_CLASS_NAME}}(num_parameters=num_parameters) if default_values.squeeze().ndim == 0: default_values = np.zeros(num_parameters-1) + default_values cdef int num_simplices = simplextree.num_simplices() @@ -1694,20 +1752,23 @@ def _safe_simplextree_multify_{{FSHORT}}2(simplextree:SimplexTree,int num_parame global available_simplextrees, SimplexTreeMulti_type available_simplextrees = tuple(( - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - SimplexTreeMulti_{{FSHORT}}, + {{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + {{PY_CLASS_NAME}}, {{endfor}} )) SimplexTreeMulti_type:type= Union[ - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - SimplexTreeMulti_{{FSHORT}}, + {{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + {{PY_CLASS_NAME}}, {{endfor}} ] def is_simplextree_multi(input)->bool: return (False - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - or isinstance(input, SimplexTreeMulti_{{FSHORT}}) + {{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + or isinstance(input, {{D["PY_CLASS_NAME"]}}) {{endfor}} ) @@ -1715,7 +1776,7 @@ def is_simplextree_multi(input)->bool: -def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False,**kwargs) -> SimplexTreeMulti_type: +def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, bool kcritical = False, ftype="Contiguous", **kwargs) -> SimplexTreeMulti_type: """SimplexTreeMulti constructor. :param other: If `other` is `None` (default value), an empty `SimplexTreeMulti` is created. @@ -1729,17 +1790,12 @@ def SimplexTreeMulti(input=None, int num_parameters=-1, dtype:type = np.float64, :raises TypeError: In case `other` is neither `None`, nor a `SimplexTree`, nor a `SimplexTreeMulti`. """ - cdef dict[tuple[type, bool], type] _st_map = { - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - (np.dtype({{PYTYPE}}),{{is_kcritical}}): SimplexTreeMulti_{{FSHORT}}, - {{endfor}} - } - # TODO : check that + kcriticality - # if input is not None: - # assert input.dtype is dtype, "SimplexTree conversions are not yet implemented" - - return _st_map[(np.dtype(dtype), kcritical)](input, num_parameters=num_parameters, **kwargs) - + {{for D in to_iter}} + # WARN: updating locals {{locals().update(D)}} + if dtype == np.dtype({{PYTYPE}}) and kcritical == {{IS_KCRITICAL}} and ftype == "{{SHORT_FILTRATION_TYPE}}": + return {{PY_CLASS_NAME}}(input, num_parameters=num_parameters, **kwargs) + {{endfor}} + raise TypeError(f"No SimplexTreeMulti implementation for dtype={dtype}, kcritical={kcritical}, ftype={ftype}.") @@ -1756,9 +1812,10 @@ python_tensor_dtype = np.int32 ctypedef pair[vector[vector[indices_type]], vector[tensor_dtype]] signed_measure_type cdef extern from "multi_parameter_rank_invariant/hilbert_function.h" namespace "Gudhi::multiparameter::hilbert_function": - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - signed_measure_type get_hilbert_signed_measure(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil - {{endfor}} +{{for D in to_iter}} + # WARN: updating locals {{locals().update(D)}} + signed_measure_type get_hilbert_signed_measure({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], const vector[indices_type], bool, indices_type, bool, bool) except + nogil +{{endfor}} @@ -1766,15 +1823,13 @@ cdef extern from "multi_parameter_rank_invariant/hilbert_function.h" namespace " ## Aligns python/cpp cdef inline signed_measure_type _hilbert_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, const vector[indices_type]& degrees, bool zero_pad, indices_type n_jobs, bool verbose, bool expand_collapse): cdef intptr_t simplextree_ptr = simplextree.thisptr - if False: - pass -{{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +{{for D in to_iter}} + # WARN: updating locals {{locals().update(D)}} + if isinstance(simplextree, {{PY_CLASS_NAME}}): with nogil: - return get_hilbert_signed_measure(dereference(simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse) + return get_hilbert_signed_measure(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, degrees, zero_pad, n_jobs, verbose, expand_collapse) {{endfor}} - else: - raise ValueError("Input {simplextree} not supported.") + raise ValueError(f"Input {simplextree} not supported.") def _hilbert_signed_measure(simplextree, vector[indices_type] degrees, @@ -1847,25 +1902,24 @@ def _hilbert_signed_measure(simplextree, cdef extern from "multi_parameter_rank_invariant/euler_characteristic.h" namespace "Gudhi::multiparameter::euler_characteristic": # void get_euler_surface_python(const intptr_t, tensor_dtype*, const vector[indices_type], bool, bool, bool) except + nogil - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - signed_measure_type get_euler_signed_measure(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil - {{endfor}} +{{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + signed_measure_type get_euler_signed_measure({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], bool, bool) except + nogil +{{endfor}} ## Aligns python/cpp cdef inline signed_measure_type _euler_sm_from_simplextree(object simplextree, tensor_dtype* container_ptr, const vector[indices_type]& c_grid_shape, bool zero_pad, bool verbose): cdef intptr_t simplextree_ptr = simplextree.thisptr - if False: - pass -{{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} -{{if not is_kcritical}} - elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +{{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + {{if not IS_KCRITICAL}} + if isinstance(simplextree, {{PY_CLASS_NAME}}): with nogil: - return get_euler_signed_measure(dereference(simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose) -{{endif}} + return get_euler_signed_measure(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, zero_pad, verbose) + {{endif}} {{endfor}} - else: - raise ValueError("Input {simplextree} not supported.") + raise ValueError(f"Input {simplextree} not supported.") def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False): @@ -1931,8 +1985,9 @@ def _euler_signed_measure(simplextree, mass_default=None, bool verbose=False): ## Rank invariant cdef extern from "multi_parameter_rank_invariant/rank_invariant.h" namespace "Gudhi::multiparameter::rank_invariant": - {{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - void compute_rank_invariant_python(Simplex_tree_multi_interface[{{FSHORT}}, {{CTYPE}}]&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil + {{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + void compute_rank_invariant_python({{ST_INTERFACE}}&, tensor_dtype* , const vector[indices_type], const vector[indices_type], indices_type, bool) except + nogil {{endfor}} @@ -1941,13 +1996,14 @@ cdef inline void _rank_sm_from_simplextree(object simplextree, tensor_dtype* con cdef intptr_t simplextree_ptr = simplextree.thisptr if False: pass -{{for CTYPE,PYTYPE,SHORT,Filtration, is_kcritical, FSHORT in to_iter}} - elif isinstance(simplextree, SimplexTreeMulti_{{FSHORT}}): +{{for D in to_iter}} + # WARN : updating locals {{locals().update(D)}} + elif isinstance(simplextree, {{PY_CLASS_NAME}}): with nogil: - compute_rank_invariant_python(dereference(simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse) + compute_rank_invariant_python(dereference(<{{ST_INTERFACE}}*>simplextree_ptr), container_ptr, c_grid_shape, degrees, n_jobs, expand_collapse) {{endfor}} else: - raise ValueError("Input {simplextree} not supported.") + raise ValueError(f"Input {simplextree} not supported.") def _rank_signed_measure(simplextree, vector[indices_type] degrees, mass_default=None, plot=False, indices_type n_jobs=0, bool verbose=False, bool expand_collapse=False): diff --git a/multipers/slicer.pxd.tp b/multipers/slicer.pxd.tp index 4d5ace86..db159d22 100644 --- a/multipers/slicer.pxd.tp +++ b/multipers/slicer.pxd.tp @@ -14,6 +14,8 @@ with open("build/tmp/_slicer_names.pkl", "rb") as f: float_value_types = set((("float", "np.float32", "f32"), ("double", "np.float64", "f64")) ) +with open("build/tmp/_simplextrees_.pkl", "rb") as f: + simplex_trees = pickle.load(f) }} @@ -41,86 +43,112 @@ cdef extern from "Simplex_tree_multi_interface.h" namespace "Gudhi::multiparamet from multipers.filtrations cimport * ctypedef vector[uint] cycle_type ## its the cycle type of matrix +cdef extern from "gudhi/Multi_parameter_filtered_complex.h" namespace "Gudhi::multi_persistence": + cdef cppclass Multi_parameter_filtered_complex[F]: + Multi_parameter_filtered_complex() + Multi_parameter_filtered_complex(vector[vector[uint32_t]]&, vector[int]&, vector[F]&) {{for D in slicers}} - #------------------------------------------------------------------------------ -cdef extern from "Persistence_slices_interface.h": +cdef extern from "Persistence_slices_interface.h" namespace "multipers::tmp_interface": + cdef cppclass Bar[T]: + pass + + cdef cppclass Barcode[S, T]: + size_t size() + Bar[T]* data() + + cdef cppclass Dim_barcode[S, T]: + size_t size() + Barcode[S,T]& operator[](size_t) + cdef cppclass {{D['C_TEMPLATE_TYPE']}} "{{D['TRUC_TYPE']}}": ctypedef {{D['C_VALUE_TYPE']}} value_type {{D['C_TEMPLATE_TYPE']}}() - - {{if D['IS_SIMPLICIAL']}} - {{D['C_TEMPLATE_TYPE']}}(Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}}, {{D['C_VALUE_TYPE']}}]*) - {{else}} - {{D['C_TEMPLATE_TYPE']}}(const vector[vector[unsigned int]]&, const vector[int]&, const vector[{{D['FILTRATION_TYPE']}}]&) - {{endif}} - + {{D['C_TEMPLATE_TYPE']}}(Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}]&) {{D['C_TEMPLATE_TYPE']}}& operator=(const {{D['C_TEMPLATE_TYPE']}}&) - - pair[{{D['C_TEMPLATE_TYPE']}}, vector[unsigned int]] colexical_rearange() except + nogil - {{D['C_TEMPLATE_TYPE']}} permute(const vector[unsigned int]&) except + nogil - - vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]] get_barcode() nogil - vector[vector[pair[int,int]]] get_barcode_idx() nogil - vector[vector[vector[pair[int,int]]]] custom_persistences({{D['C_VALUE_TYPE']}}*, int size, bool ignore_inf) except + nogil - void push_to(const Line[{{D['C_VALUE_TYPE']}}]&) nogil - void set_one_filtration(const vector[{{D['C_VALUE_TYPE']}}]&) nogil - int prune_above_dimension(int) except + nogil - - vector[{{D['C_VALUE_TYPE']}}] get_one_filtration() - # void compute_persistence(vector[bool]) except+ nogil - void compute_persistence(bool) except+ nogil # ignore_inf - void compute_persistence() except+ nogil # ignore_inf - uint32_t num_generators() nogil - uint32_t num_parameters() nogil - string to_str() nogil - pair[One_critical_filtration[{{D['C_VALUE_TYPE']}}], One_critical_filtration[{{D['C_VALUE_TYPE']}}]] get_bounding_box() except + nogil - vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] get_filtration_values() nogil - vector[int] get_dimensions() nogil - int get_dimension(int i) nogil - const vector[vector[uint]]& get_boundaries() nogil - void coarsen_on_grid_inplace(vector[vector[{{D['C_VALUE_TYPE']}}]], bool) nogil - vector[{{D['FILTRATION_TYPE']}}]& get_filtrations() nogil - {{if D['COLUMN_TYPE'] is not None}} - {{D['C_TEMPLATE_TYPE'][:-3]+"i32"}} coarsen_on_grid(vector[vector[{{D['C_VALUE_TYPE']}}]]) nogil - {{endif}} + uint32_t num_generators "get_number_of_cycle_generators"() nogil + uint32_t num_parameters "get_number_of_parameters"() nogil {{if D['IS_VINE']}} - void vineyard_update() nogil - vector[vector[vector[vector[unsigned int]]]] get_representative_cycles(bool, bool) nogil vector[uint32_t] get_current_order() nogil + void vineyard_update() nogil + vector[vector[vector[uint32_t]]] get_representative_cycles(bool) nogil + vector[uint32_t] get_most_persistent_cycle(int, bool) nogil {{endif}} + vector[{{D['C_VALUE_TYPE']}}] get_one_filtration "get_slice"() + pair[{{D['FILTRATION_TYPE']}}, {{D['FILTRATION_TYPE']}}] get_bounding_box() except + nogil + vector[{{D['FILTRATION_TYPE']}}]& get_filtrations "get_filtration_values"() nogil + vector[int] get_dimensions() nogil + int get_dimension(uint32_t) nogil + const vector[vector[uint32_t]]& get_boundaries() nogil + vector[uint32_t] get_boundary(uint32_t) nogil + void set_one_filtration "set_slice"(const vector[{{D['C_VALUE_TYPE']}}]&) nogil + void push_to(const Line[{{D['C_VALUE_TYPE']}}]&) nogil + int prune_above_dimension(int) except + nogil + void coarsen_on_grid_inplace "coarsen_on_grid"(vector[vector[{{D['C_VALUE_TYPE']}}]], bool) nogil + void initialize_persistence_computation(bool) except+ nogil + void initialize_persistence_computation() except+ nogil # ignore_inf = true + Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}] get_barcode "get_flat_barcode"() nogil + Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, int] get_barcode_idx "get_flat_barcode"() nogil + string slicer_to_str({{D['C_TEMPLATE_TYPE']}}&) nogil #to_str + {{D['C_TEMPLATE_TYPE']}} build_permuted_slicer "build_permuted_slicer"({{D['C_TEMPLATE_TYPE']}}&, vector[uint32_t]&) except + nogil #permute + pair[{{D['C_TEMPLATE_TYPE']}}, vector[uint32_t]] build_permuted_slicer "build_permuted_slicer"({{D['C_TEMPLATE_TYPE']}}&) except + nogil #colexical_rearange + {{if D['COLUMN_TYPE'] is not None}} + {{D['C_TEMPLATE_TYPE'][:-3]+"i32"}} build_slicer_coarsen_on_grid "build_slicer_coarsen_on_grid"({{D['C_TEMPLATE_TYPE']}}&, vector[vector[{{D['C_VALUE_TYPE']}}]]&) nogil #coarsen_on_grid + {{endif}} + {{if D['COLUMN_TYPE'] is not None}} + {{D['C_TEMPLATE_TYPE']}} build_slicer_from_projective_cover_kernel "build_slicer_from_projective_cover_kernel"({{D['C_TEMPLATE_TYPE']}}&, int) except + nogil #projective_cover_kernel + {{endif}} + void write_slicer_to_scc_file "write_slicer_to_scc_file"(string&, {{D['C_TEMPLATE_TYPE']}}&, int, bool, bool, bool, bool) nogil #write_to_scc_file - {{if D['IS_KCRITICAL']}} - void add_generator(const One_critical_filtration[{{D['C_VALUE_TYPE']}}] &) nogil - {{endif}} - - void write_to_scc_file(const string&, int, int, bool, bool, bool, bool) nogil - {{if not D['IS_KCRITICAL']}} - void build_from_scc_file(const string&, bool, bool, int) except + nogil - {{endif}} - - - vector[vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]]] persistence_on_lines(vector[vector[{{D['C_VALUE_TYPE']}}]], bool) except + nogil - vector[vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]]] persistence_on_lines(vector[pair[vector[{{D['C_VALUE_TYPE']}}],vector[{{D['C_VALUE_TYPE']}}]]],bool) except + nogil - +#------------------------------------------------------------------------------ +cdef extern from "gudhi/slicer_helpers.h" namespace "Gudhi::multi_persistence": + {{if not D['IS_KCRITICAL']}} + {{D['C_TEMPLATE_TYPE']}} build_slicer_from_scc_file_{{D['C_TEMPLATE_TYPE']}} "Gudhi::multi_persistence::build_slicer_from_scc_file<{{D['TRUC_TYPE']}}>"(string&, bool, bool, int) except + nogil #build_from_scc_file + {{endif}} + {{for ST in simplex_trees}} + {{D['C_TEMPLATE_TYPE']}} build_slicer_from_simplex_tree_{{D['C_TEMPLATE_TYPE']}}_{{ST["PyFil"]}} "Gudhi::multi_persistence::build_slicer_from_simplex_tree<{{D['TRUC_TYPE']}}>"({{ST["ST_INTERFACE"]}}&) except + nogil + {{endfor}} + {{D['C_TEMPLATE_TYPE']}} build_slicer_from_bitmap_{{D['C_TEMPLATE_TYPE']}} "Gudhi::multi_persistence::build_slicer_from_bitmap<{{D['TRUC_TYPE']}}>"(vector[{{D['FILTRATION_TYPE']}}], vector[unsigned int]) except + nogil + vector[Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, int]] custom_persistences "persistence_on_slices<{{D['TRUC_TYPE']}}, {{D['C_VALUE_TYPE']}}, int, true>"({{D['C_TEMPLATE_TYPE']}}&, {{D['C_VALUE_TYPE']}}*, int, bool) except + nogil + vector[Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]] persistence_on_lines "persistence_on_slices"({{D['C_TEMPLATE_TYPE']}}&, vector[vector[{{D['C_VALUE_TYPE']}}]]&, vector[vector[{{D['C_VALUE_TYPE']}}]]&, bool) except + nogil + +cdef inline _b2np_{{D['C_TEMPLATE_TYPE']}}(const Barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]& barcode): + cdef Py_ssize_t size = barcode.size() * 2 + if size == 0: + return np.empty(shape=(0, 2), dtype={{D['PY_VALUE_TYPE']}}) + cdef {{D['C_VALUE_TYPE']}}[:] view = <{{D['C_VALUE_TYPE']}}[:size]>(<{{D['C_VALUE_TYPE']}}*>(barcode.data())) + #copy as the barcode will very probably be destroyed afterwards + return np.array(view).reshape((-1, 2)) + +cdef inline _db2np_{{D['C_TEMPLATE_TYPE']}}(const Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]& barcode): + cdef Py_ssize_t dims = barcode.size() + return [_b2np_{{D['C_TEMPLATE_TYPE']}}(barcode[i]) for i in range(dims)] + +cdef inline _b2np_{{D['C_TEMPLATE_TYPE']}}_idx(const Barcode[{{D['C_TEMPLATE_TYPE']}}, int]& barcode): + cdef Py_ssize_t size = barcode.size() * 2 + if size == 0: + return np.empty(shape=(0, 2), dtype=np.dtype(int)) + cdef int[:] view = ((barcode.data())) + #copy as the barcode will very probably be destroyed afterwards + return np.array(view).reshape((-1, 2)) + +cdef inline _db2np_{{D['C_TEMPLATE_TYPE']}}_idx(const Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, int]& barcode): + cdef Py_ssize_t dims = barcode.size() + return [_b2np_{{D['C_TEMPLATE_TYPE']}}_idx(barcode[i]) for i in range(dims)] - {{if D['COLUMN_TYPE'] is not None}} - {{D['C_TEMPLATE_TYPE']}} projective_cover_kernel(int dim) except + nogil - {{endif}} {{endfor}} - #### MMA Stuff from multipers.mma_structures cimport Module cdef extern from "multiparameter_module_approximation/approximation.h" namespace "Gudhi::multiparameter::mma": {{for D in slicers}} {{if D['IS_VINE'] and D['IS_FLOAT']}} - Module[{{D['C_VALUE_TYPE']}}] multiparameter_module_approximation({{D['C_TEMPLATE_TYPE']}}&, One_critical_filtration[{{D['C_VALUE_TYPE']}}]&, {{D['C_VALUE_TYPE']}}, Box[{{D['C_VALUE_TYPE']}}]&, bool, bool, bool) except + nogil + Module[{{D['C_VALUE_TYPE']}}] multiparameter_module_approximation({{D['C_TEMPLATE_TYPE']}}&, vector[{{D['C_VALUE_TYPE']}}]&, {{D['C_VALUE_TYPE']}}, Box[{{D['C_VALUE_TYPE']}}]&, bool, bool, bool) except + nogil {{endif}} {{endfor}} pass @@ -131,7 +159,7 @@ cdef extern from "multiparameter_module_approximation/approximation.h" namespace import multipers.slicer as mps from cython.operator cimport dereference {{for C_VALUE_TYPE,PYTHON_VALUE_TYPE,SHORT_VALUE_TYPE in float_value_types}} -cdef inline Module[{{C_VALUE_TYPE}}] _multiparameter_module_approximation_{{SHORT_VALUE_TYPE}}(object slicer, One_critical_filtration[{{C_VALUE_TYPE}}] direction, {{C_VALUE_TYPE}} max_error, Box[{{C_VALUE_TYPE}}] box, bool threshold, bool complete, bool verbose): +cdef inline Module[{{C_VALUE_TYPE}}] _multiparameter_module_approximation_{{SHORT_VALUE_TYPE}}(object slicer, vector[{{C_VALUE_TYPE}}] direction, {{C_VALUE_TYPE}} max_error, Box[{{C_VALUE_TYPE}}] box, bool threshold, bool complete, bool verbose): import multipers.slicer as mps cdef intptr_t slicer_ptr = (slicer.get_ptr()) cdef Module[{{C_VALUE_TYPE}}] mod diff --git a/multipers/slicer.pyx.tp b/multipers/slicer.pyx.tp index 6316d1d0..921189cd 100644 --- a/multipers/slicer.pyx.tp +++ b/multipers/slicer.pyx.tp @@ -13,6 +13,9 @@ with open("build/tmp/_slicer_names.pkl", "rb") as f: dtypes = set([(D['PY_VALUE_TYPE'], D['C_VALUE_TYPE'], D['SHORT_VALUE_TYPE']) for D in slicers]) +with open("build/tmp/_simplextrees_.pkl", "rb") as f: + simplex_trees = pickle.load(f) + }} from multipers.simplex_tree_multi import SimplexTreeMulti, SimplexTreeMulti_type @@ -30,7 +33,7 @@ from multipers.filtration_conversions cimport * from multipers.point_measure import sparsify, rank_decomposition_by_rectangles from multipers.grids import compute_grid, sanitize_grid, evaluate_in_grid, _push_pts_to_lines, _inf_value from multipers.array_api import api_from_tensor, api_from_tensors - +import multipers.simplex_tree_multi as _st_ import numpy as np cimport cython from libcpp.string cimport string @@ -39,6 +42,8 @@ from typing import Union from cython.operator cimport dereference import time +from libcpp.algorithm cimport sort + ## WARNING : This is repeated in the pxd file ... python_indices_type=np.int32 python_tensor_dtype = np.int32 @@ -73,6 +78,18 @@ available_pers_backend = set([ ]) +global available_filtration_container +available_filtration_container = set([ +{{for D in slicers}} + "{{D['SHORT_FILTRATION_TYPE']}}", +{{endfor}} +]) +_filtration_container_type = Literal[ +{{for D in slicers}} + "{{D['FILTRATION_CONTAINER_STR']}}", +{{endfor}} +] + global column_type _column_type = Literal[ @@ -104,6 +121,7 @@ _valid_pers_backend = Literal[ ] {{for D in slicers}} +# WARN : updating locals {{locals().update(D)}} #------------------------------------------------------------------------------ cdef class {{D['PYTHON_TYPE']}}: @@ -120,6 +138,9 @@ cdef class {{D['PYTHON_TYPE']}}: @property def is_minpres(self)->bool: return self.minpres_degree>=0 + @property + def ftype(self)->str: + return "{{FILTRATION_TYPE}}" @staticmethod def _inf_value(): return np.asarray(np.inf,dtype={{D['PY_VALUE_TYPE']}}) if issubclass({{D['PY_VALUE_TYPE']}},np.floating) else np.iinfo({{D['PY_VALUE_TYPE']}}).max @@ -144,7 +165,7 @@ cdef class {{D['PYTHON_TYPE']}}: return cdef intptr_t ptr = st.thisptr cdef Simplex_tree_multi_interface[{{D['FILTRATION_TYPE']}},{{D['C_VALUE_TYPE']}}]* st_ptr = (ptr) - self.truc = {{D['C_TEMPLATE_TYPE']}}(st_ptr) + self.truc = build_slicer_from_simplex_tree_{{D['C_TEMPLATE_TYPE']}}_{{D['FILTRATION_TYPE']}}(dereference(st_ptr)) self.minpres_degree = -1 {{elif D['IS_KCRITICAL']}} def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]): @@ -161,6 +182,9 @@ cdef class {{D['PYTHON_TYPE']}}: """ Cython constructor """ + if len(generator_maps) == 0: + self.minpres_degree = -1 + return if len(generator_maps)>0 and len(generator_dimensions) == 0 and len(filtration_values) == 0: from multipers._slicer_meta import _blocks2boundary_dimension_grades generator_maps, generator_dimensions, filtration_values = _blocks2boundary_dimension_grades( @@ -168,23 +192,26 @@ cdef class {{D['PYTHON_TYPE']}}: inplace=False, ) cdef uint32_t num_generators = len(generator_maps) + filtration_values = [np.asarray(f, dtype = {{D['PY_VALUE_TYPE']}} ) for f in filtration_values] + cdef uint32_t num_param = filtration_values[0].shape[1] cdef vector[vector[uint32_t]] c_generator_maps - cdef vector[Multi_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values + cdef vector[{{D['FILTRATION_TYPE']}}] c_filtration_values for stuff in generator_maps: c_generator_maps.push_back((stuff)) - cdef Multi_critical_filtration[{{D['C_VALUE_TYPE']}}] cf - cdef One_critical_filtration[{{D['C_VALUE_TYPE']}}] inf - inf[0] = -inf[0] + sort(c_generator_maps.back().begin(), c_generator_maps.back().end()) + cdef {{D['FILTRATION_TYPE']}} cf = {{D['FILTRATION_TYPE']}}(num_param) + cdef {{D['FILTRATION_TYPE']}} inf = {{D['FILTRATION_TYPE']}}.inf(num_param) cdef {{D['C_VALUE_TYPE']}}[:,:] F_view for F in filtration_values: - cf.push_to_least_common_upper_bound(inf) + cf.push_to_least_common_upper_bound(inf, False) F_view = np.asarray(F, dtype = {{D['PY_VALUE_TYPE']}} ) for i in range(F_view.shape[0]): - cf.add_generator(_py21c_{{D['SHORT_VALUE_TYPE']}}(F_view[i])) + cf.add_generator(_py2p_{{D['SHORT_VALUE_TYPE']}}(F_view[i])) c_filtration_values.push_back(cf) cdef vector[int] c_generator_dimensions = generator_dimensions assert num_generators == c_generator_maps.size() == c_filtration_values.size(), "Invalid input, shape do not coincide." - self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values) + cdef Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}] cpx = Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}](c_generator_maps,c_generator_dimensions, c_filtration_values) + self.truc = {{D['C_TEMPLATE_TYPE']}}(cpx) self.minpres_degree = -1 {{else}} def __init__(self, generator_maps=[], generator_dimensions=[], filtration_values=[]): @@ -205,24 +232,26 @@ cdef class {{D['PYTHON_TYPE']}}: filtration_values = np.asarray(filtration_values, dtype = {{D['PY_VALUE_TYPE']}} ) assert len(filtration_values) == num_generators, f"Invalid input, shape do not coicide. Got sizes {num_generators,len(generator_dimensions),len(filtration_values)}." cdef vector[vector[uint32_t]] c_generator_maps - cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] c_filtration_values + cdef vector[{{D['FILTRATION_TYPE']}}] c_filtration_values c_generator_maps.resize(num_generators) c_filtration_values.resize(num_generators) for i in range(num_generators): c_generator_maps[i] = (generator_maps[i]) + sort(c_generator_maps[i].begin(), c_generator_maps[i].end()) c_filtration_values[i] = _py21c_{{D['SHORT_VALUE_TYPE']}}(filtration_values[i]) cdef vector[int] c_generator_dimensions = generator_dimensions assert num_generators == c_generator_maps.size() == c_filtration_values.size() == c_generator_dimensions.size(), "Invalid input, shape do not coincide." - self.truc = {{D['C_TEMPLATE_TYPE']}}(c_generator_maps,c_generator_dimensions, c_filtration_values) + cdef Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}] cpx = Multi_parameter_filtered_complex[{{D['FILTRATION_TYPE']}}](c_generator_maps,c_generator_dimensions, c_filtration_values) + self.truc = {{D['C_TEMPLATE_TYPE']}}(cpx) self.minpres_degree = -1 {{endif}} def to_colexical(self, bool return_permutation = False)->{{D['PYTHON_TYPE']}}|tuple[{{D['PYTHON_TYPE']}},np.ndarray]: # assert not self.is_squeezed, "Unsqueeze first, this is not implented yet for squeezed slicers" new_slicer = {{D['PYTHON_TYPE']}}() - cdef pair[{{D['C_TEMPLATE_TYPE']}}, vector[unsigned int]] stuff = self.truc.colexical_rearange() + cdef pair[{{D['C_TEMPLATE_TYPE']}}, vector[uint32_t]] stuff = build_permuted_slicer(self.truc) new_slicer.truc = stuff.first new_slicer.minpres_degree = self.minpres_degree @@ -232,9 +261,9 @@ cdef class {{D['PYTHON_TYPE']}}: return new_slicer, np.array(stuff.second, dtype=np.int32) return new_slicer def permute_generators(self, permutation)->{{D['PYTHON_TYPE']}}: - cdef vector[unsigned int] c_perm = permutation + cdef vector[uint32_t] c_perm = permutation new_slicer = {{D['PYTHON_TYPE']}}() - new_slicer.truc = self.truc.permute(c_perm) + new_slicer.truc = build_permuted_slicer(self.truc, c_perm) new_slicer.minpres_degree = self.minpres_degree return new_slicer @@ -252,8 +281,9 @@ cdef class {{D['PYTHON_TYPE']}}: return {{D['PYTHON_TYPE']}}() if dim is None: dim = self.truc.get_dimension(0) + cdef int cdim = dim out = {{D['PYTHON_TYPE']}}() - out.truc = self.truc.projective_cover_kernel(dim) + out.truc = build_slicer_from_projective_cover_kernel(self.truc, cdim) out.filtration_grid = self.filtration_grid return out @@ -261,13 +291,13 @@ cdef class {{D['PYTHON_TYPE']}}: """ Returns the current barcode. """ - return tuple(np.asarray(x) if len(x) else np.empty((0,2), dtype=int)for x in self.truc.get_barcode_idx()) + return tuple(np.asarray(x) if len(x) else np.empty((0,2), dtype=int)for x in _db2np_{{D['C_TEMPLATE_TYPE']}}_idx(self.truc.get_barcode_idx())) def get_barcode(self, bool keep_inf = False): """ Returns the current barcode. """ if keep_inf: - bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode()) + bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode())) else: bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode()) return bcs @@ -280,10 +310,10 @@ cdef class {{D['PYTHON_TYPE']}}: basepoint = np.asarray(basepoint, dtype = {{D['PY_VALUE_TYPE']}}) cdef Line[{{D['C_VALUE_TYPE']}}] line if direction is None: - line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint)) + line = Line[{{D['C_VALUE_TYPE']}}](_py2p_{{D['SHORT_VALUE_TYPE']}}(basepoint)) else: direction = np.asarray(direction, dtype = {{D['PY_VALUE_TYPE']}}) - line = Line[{{D['C_VALUE_TYPE']}}](_py21c_{{D['SHORT_VALUE_TYPE']}}(basepoint),_py21c_{{D['SHORT_VALUE_TYPE']}}(direction)) + line = Line[{{D['C_VALUE_TYPE']}}](_py2p_{{D['SHORT_VALUE_TYPE']}}(basepoint),_py2p_{{D['SHORT_VALUE_TYPE']}}(direction)) self.truc.push_to(line) return self {{else}} @@ -291,8 +321,14 @@ cdef class {{D['PYTHON_TYPE']}}: {{endif}} @staticmethod - cdef _threshold_bcs(vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]] bcs): - return tuple(np.fromiter((a for a in stuff if a.first < {{D['PYTHON_TYPE']}}._inf_value()), dtype=np.dtype(({{D['PY_VALUE_TYPE']}},2))) for stuff in bcs) + cdef _threshold_bcs(const Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]& bcs): + return tuple( + np.fromiter( + (a for a in stuff if a[0] < {{D['PYTHON_TYPE']}}._inf_value()), + dtype=np.dtype(({{D['PY_VALUE_TYPE']}},2)) + ) + for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(bcs) + ) @staticmethod def _bc_to_full(bcs, basepoint, direction=None): # i, (b sv d), coords @@ -308,9 +344,9 @@ cdef class {{D['PYTHON_TYPE']}}: """ {{if D['IS_FLOAT']}} self.push_to_line(basepoint,direction) - self.truc.compute_persistence(ignore_infinite_filtration_values) + self.truc.initialize_persistence_computation(ignore_infinite_filtration_values) if keep_inf: - bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in self.truc.get_barcode()) + bcs = tuple(np.asarray(stuff, dtype = {{D['PY_VALUE_TYPE']}}) for stuff in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode())) else: bcs = {{D['PYTHON_TYPE']}}._threshold_bcs(self.truc.get_barcode()) @@ -348,30 +384,29 @@ cdef class {{D['PYTHON_TYPE']}}: if arr_view.shape[1] != self.truc.num_generators(): raise ValueError(f"Got filtration array of shape {filtration_array.shape=} / {arr_view.shape=}. Was expecting (-1, {len(self)=})") - return tuple(tuple(np.array(bc_idx_degree, dtype=int) for bc_idx_degree in bc_idx) for bc_idx in self.truc.custom_persistences(&arr_view[0,0], size, ignore_inf)) + return tuple(tuple(np.array(bc_idx_degree, dtype=int) for bc_idx_degree in _db2np_{{D['C_TEMPLATE_TYPE']}}_idx(bc_idx)) for bc_idx in custom_persistences(self.truc, &arr_view[0,0], size, ignore_inf)) def persistence_on_lines(self, basepoints=None, directions=None, bool keep_inf=True, bool full=False, bool ignore_infinite_filtration_values = True): """ Same as `persistence_on_line`, but with vineyards operation between lines if `self.is_vine`, and in parallel otherwise. """ - cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_basepoints + cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_basepoints = basepoints + cdef vector[vector[{{D['C_VALUE_TYPE']}}]] c_directions cdef vector[pair[vector[{{D['C_VALUE_TYPE']}}], vector[{{D['C_VALUE_TYPE']}}]]] c_truc - cdef vector[vector[vector[pair[{{D['C_VALUE_TYPE']}}, {{D['C_VALUE_TYPE']}}]]]] c_out - if directions is None: - c_basepoints = basepoints - with nogil: - c_out = self.truc.persistence_on_lines(c_basepoints, ignore_infinite_filtration_values) - else: - c_truc = zip(basepoints,directions) - with nogil: - c_out = self.truc.persistence_on_lines(c_truc, ignore_infinite_filtration_values) + cdef vector[Dim_barcode[{{D['C_TEMPLATE_TYPE']}}, {{D['C_VALUE_TYPE']}}]] c_out + if directions is not None: + c_directions = directions + assert c_directions.size() == c_basepoints.size(), "Not as many directions than base points" + + with nogil: + c_out = persistence_on_lines(self.truc, c_basepoints, c_directions, ignore_infinite_filtration_values) cdef int num_bc = c_basepoints.size() if keep_inf: out = tuple(tuple( np.asarray(y, dtype = {{D['PY_VALUE_TYPE']}}) if len(y)>0 else np.empty((0,2), dtype = {{D['PY_VALUE_TYPE']}}) - for y in x) for x in c_out) + for y in _db2np_{{D['C_TEMPLATE_TYPE']}}(x)) for x in c_out) else: out = tuple({{D['PYTHON_TYPE']}}._threshold_bcs(x) for x in c_out) @@ -455,16 +490,16 @@ cdef class {{D['PYTHON_TYPE']}}: # TODO: Later # if len(degrees)>0: - # self.truc.compute_persistence(degrees) + # self.truc.initialize_persistence_computation(degrees) # else: - # self.truc.compute_persistence() - self.truc.compute_persistence(ignore_infinite_filtration_values) + # self.truc.initialize_persistence_computation() + self.truc.initialize_persistence_computation(ignore_infinite_filtration_values) return self.get_barcode() def get_barcode(self): """ Returns the barcode of the current 1d-persistence. """ - return tuple(np.asarray(bc) for bc in self.truc.get_barcode()) + return tuple(np.asarray(bc) for bc in _db2np_{{D['C_TEMPLATE_TYPE']}}(self.truc.get_barcode())) def sliced_filtration(self,basepoint, direction=None): """ Computes the filtration on a line L defined by @@ -483,22 +518,44 @@ cdef class {{D['PYTHON_TYPE']}}: return self.truc.num_parameters() @property def info(self): - print(self.truc.to_str().decode()) + print(slicer_to_str(self.truc).decode()) def filtration_bounds(self) -> np.ndarray: """ Computes the bounding box of the current multifiltration. """ - cdef pair[One_critical_filtration[{{D['C_VALUE_TYPE']}}],One_critical_filtration[{{D['C_VALUE_TYPE']}}]] box = self.truc.get_bounding_box() - cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] a = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.first) - cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] b = _ff21cview_{{D['SHORT_VALUE_TYPE']}}(&box.second) + cdef pair[{{D['FILTRATION_TYPE']}}, {{D['FILTRATION_TYPE']}}] box = self.truc.get_bounding_box() + + {{if "Flat" in D['FILTRATION_TYPE']}} + + a = np.asarray([box.first(0,0), box.first(0,1)]) + g = box.second.num_generators() - 1 + b = np.asarray([box.second(g,0), box.second(g,1)]) + + {{else}} + + if box.first.is_finite(): + duplicate = 0 + else: + duplicate = box.first.num_parameters() + cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] a = _ff21cview2_{{D['SHORT_VALUE_TYPE']}}(&box.first(0,0), box.first.num_parameters(), duplicate, True) + + if box.second.is_finite(): + duplicate = 0 + else: + duplicate = box.second.num_parameters() + cdef cnp.ndarray[{{D['C_VALUE_TYPE']}}, ndim=1] b = _ff21cview2_{{D['SHORT_VALUE_TYPE']}}(&box.second(0,0), box.first.num_parameters(), duplicate, True) + + {{endif}} + return np.asarray([a,b]) + def get_filtrations_values(self)->np.ndarray: """ Returns the current filtration values of the slicer. """ - cdef vector[One_critical_filtration[{{D['C_VALUE_TYPE']}}]] v = self.truc.get_filtration_values() - out = _vff21cview_{{D['SHORT_VALUE_TYPE']}}(v, copy=True, duplicate=self.num_parameters) - return np.asarray(out) + cdef vector[{{D['FILTRATION_TYPE']}}] v = self.truc.get_filtrations() + out = [[v[i](g,p) for p in range(self.num_parameters)] for i in range(v.size()) for g in range(v[i].num_generators())] + return np.asarray(out, dtype={{D['PY_VALUE_TYPE']}}) def get_filtration_grid(self,grid_strategy:str="exact", **infer_grid_kwargs): return compute_grid( self.get_filtrations_values().T, @@ -512,9 +569,9 @@ cdef class {{D['PYTHON_TYPE']}}: {{if D['IS_KCRITICAL']}} if unsqueeze: raise NotImplementedError("Unsqueezed version not implemented for multicritical filtrations.") - return _vff2kcview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters) + return vect_{{D['FILTRATION_TYPE']}}_2_python(self.truc.get_filtrations(), copy=False) {{else}} - out = _vff21cview_{{D['SHORT_VALUE_TYPE']}}(self.truc.get_filtrations(), copy=False, duplicate=self.num_parameters) + out = vect_{{D['FILTRATION_TYPE']}}_2_python(self.truc.get_filtrations(), copy=False) if not unsqueeze: return out if not self.is_squeezed: @@ -552,15 +609,15 @@ cdef class {{D['PYTHON_TYPE']}}: cdef vector[int] dims = self.truc.get_dimensions() cdef Py_ssize_t num_parameters = self.num_parameters cdef vector[vector[unsigned int]] B = self.truc.get_boundaries() # const issues, I was lazy + cdef {{D['C_VALUE_TYPE']}}* f with nogil: for i in range(N): for b in B[i]: for j in range(num_parameters): - self.truc.get_filtrations()[b][j] = max( - self.truc.get_filtrations()[b][j], - self.truc.get_filtrations()[i][j] - ) + # Weird cython bug with "Cannot assign to or delete this" + f = &(self.truc.get_filtrations()[b](0,j)) + f[0] = max(f[0], self.truc.get_filtrations()[i](0,j)) return self @@ -617,7 +674,7 @@ cdef class {{D['PYTHON_TYPE']}}: raise ValueError("WIP") {{else}} out = {{D['PYTHON_TYPE'][:-3]+"i32"}}() - out.truc = self.truc.coarsen_on_grid(grid) + out.truc = build_slicer_coarsen_on_grid(self.truc, grid) if coordinates: out.filtration_grid = sanitize_grid(filtration_grid) out.minpres_degree = self.minpres_degree @@ -662,6 +719,9 @@ cdef class {{D['PYTHON_TYPE']}}: def col_type(self)->str: return "{{D['COLUMN_TYPE']}}" @property + def filtration_container(self)->str: + return "{{D['SHORT_FILTRATION_TYPE']}}" + @property def is_vine(self)->bool: return {{D['IS_VINE']}} @property @@ -680,12 +740,50 @@ cdef class {{D['PYTHON_TYPE']}}: self.push_to_line(basepoint,direction) self.truc.vineyard_update() return self - def get_representative_cycles(self, bool update=True, bool detailed=False): + def get_representative_cycles(self, bool update=True): """ Returns the representative cycles of the current barcode. Recomputes the generators if update=True """ - return self.truc.get_representative_cycles(update, detailed) + cdef vector[vector[vector[uint32_t]]] cycle_idx = self.truc.get_representative_cycles(update) + cdef vector[vector[vector[vector[uint32_t]]]] out = vector[vector[vector[vector[uint32_t]]]](cycle_idx.size()) + + with nogil: + for i in range(cycle_idx.size()): + out[i].resize(cycle_idx[i].size()) + for j in range(cycle_idx[i].size()): + if cycle_idx[i][j].size() > 0: + # to match the old format, otherwise we can have several empty boundaries for one cycle + if self.truc.get_boundary(cycle_idx[i][j][0]).empty(): + out[i][j].push_back(vector[uint32_t]()) + else: + out[i][j].resize(cycle_idx[i][j].size()) + for k in range(cycle_idx[i][j].size()): + out[i][j][k] = self.truc.get_boundary(cycle_idx[i][j][k]) + return out + def get_most_persistent_cycle(self, int dim=1, bool update=True, bool idx=False): + """ + Returns the representative cycles with longest bar in the given dimension. + Recomputes the generators if update=True + """ + cdef vector[uint32_t] cycle_idx = self.truc.get_most_persistent_cycle(dim, update) + + if idx: + return cycle_idx + + cdef vector[vector[uint32_t]] out + + with nogil: + if cycle_idx.size() > 0: + # to match the old format, otherwise we can have several empty boundaries for one cycle + if self.truc.get_boundary(cycle_idx[0]).empty(): + out.push_back(vector[uint32_t]()) + else: + out.resize(cycle_idx.size()) + for k in range(cycle_idx.size()): + out[k] = self.truc.get_boundary(cycle_idx[k]) + return out + def get_permutation(self): """ Returns the current generator permutation (w.r.t. vineyard). @@ -696,7 +794,6 @@ cdef class {{D['PYTHON_TYPE']}}: def to_scc( self, path:os.PathLike, - int num_parameters = -1, int degree = -1, bool rivet_compatible = False, bool ignore_last_generators = False, @@ -717,7 +814,7 @@ cdef class {{D['PYTHON_TYPE']}}: self.unsqueeze().to_scc(**kwargs) return with nogil: - self.truc.write_to_scc_file(c_path, num_parameters, degree, rivet_compatible, ignore_last_generators, strip_comments, reverse) + write_slicer_to_scc_file(c_path, self.truc, degree, rivet_compatible, ignore_last_generators, strip_comments, reverse) {{if not D['IS_KCRITICAL']}} def _build_from_scc_file(self, path:os.PathLike, bool rivet_compatible = False, bool reverse = False, int shift_dimension = 0)->{{D['PYTHON_TYPE']}}: @@ -726,7 +823,7 @@ cdef class {{D['PYTHON_TYPE']}}: """ cdef string c_path = path.encode(encoding="utf-8") with nogil: - self.truc.build_from_scc_file(c_path, rivet_compatible, reverse, shift_dimension) + self.truc = build_slicer_from_scc_file_{{D['C_TEMPLATE_TYPE']}}(c_path, rivet_compatible, reverse, shift_dimension) return self def unsqueeze(self, grid=None)->{{D['PYTHON_TYPE'][:-3]+"f64"}}: @@ -742,54 +839,47 @@ cdef class {{D['PYTHON_TYPE']}}: return new_slicer {{endif}} + def build_from_simplex_tree(self, st): + cdef intptr_t ptr = st.thisptr + # st_name = type(st).__name__ + # f_short = st_name[17:] + {{for ST in simplex_trees}} + cdef {{ST["ST_INTERFACE"]}}* st_ptr_{{ST["PyFil"]}} = <{{ST["ST_INTERFACE"]}}*>(ptr) + if _st_.{{ST["PY_CLASS_NAME"]}}._isinstance(st): + self.truc = build_slicer_from_simplex_tree_{{D['C_TEMPLATE_TYPE']}}_{{ST["PyFil"]}}(dereference(st_ptr_{{ST["PyFil"]}})) + self.minpres_degree = -1 + return self + {{endfor}} + raise ValueError(f"Invalid st {st}.") {{endfor}} from libcpp.vector cimport vector from libcpp.set cimport set as cset -cdef extern from "gudhi/cubical_to_boundary.h" namespace "": - void _to_boundary(const vector[unsigned int]&, vector[vector[unsigned int]]&, vector[int]&) except + nogil - void get_vertices(unsigned int, cset[unsigned int]&, const vector[vector[unsigned int]]&) nogil - +# cdef extern from "gudhi/cubical_to_boundary.h" namespace "": +# void _to_boundary(const vector[unsigned int]&, vector[vector[unsigned int]]&, vector[int]&) except + nogil +# void get_vertices(unsigned int, cset[unsigned int]&, const vector[vector[unsigned int]]&) nogil -{{for pytype,ctype,fshort in dtypes}} -def _from_bitmap{{fshort}}(image, **slicer_kwargs): +{{for D in slicers}} +def _from_bitmap{{D['SHORT_VALUE_TYPE']}}(image, **slicer_kwargs): from multipers import Slicer dtype = slicer_kwargs.get("dtype", image.dtype) slicer_kwargs["dtype"] = dtype if image.dtype != dtype: raise ValueError(f"Invalid type matching. Got {dtype=} and {image.dtype=}") _Slicer = Slicer(return_type_only=True, **slicer_kwargs) - cdef vector[unsigned int] img_shape = image.shape[:-1] + cdef unsigned int num_parameters = image.shape[-1] - cdef vector[vector[unsigned int]] gen_maps - cdef vector[int] gen_dims - - with nogil: - _to_boundary(img_shape,gen_maps, gen_dims) - - cdef cset[unsigned int] vertices - - cdef unsigned int num_gens = gen_dims.size() - filtration_values = np.zeros(shape=(num_gens, num_parameters), dtype = {{pytype}}) - _Slicer._inf_value() - cdef {{ctype}}[:,:] F = filtration_values - cdef {{ctype}}[:,:] c_img = image.reshape(-1,num_parameters) - with nogil: - for i in range(num_gens): - # with gil: - # print(f"idx {i}:", end="") - vertices.clear() - get_vertices(i,vertices,gen_maps) - - # with gil: - # print(f"v = {vertices}:", end="") - for k in vertices: - for j in range(num_parameters): - F[i,j] = max(F[i,j], c_img[k,j]) - - # with gil: - # print(f"F = {np.asarray(F[i])}") - slicer = _Slicer(gen_maps, gen_dims, filtration_values) + cdef vector[unsigned int] img_shape = image.shape[:-1] + {{if D['IS_KCRITICAL']}} + cdef vector[{{D['FILTRATION_TYPE']}}] vertices = python_2_vect_{{D['FILTRATION_TYPE']}}(image.reshape(-1,num_parameters)) + {{else}} + cdef vector[{{D['FILTRATION_TYPE']}}] vertices = python_2_vect_{{D['FILTRATION_TYPE']}}(image.reshape(-1,num_parameters)) + {{endif}} + cdef {{D['C_TEMPLATE_TYPE']}} cslicer = build_slicer_from_bitmap_{{D['C_TEMPLATE_TYPE']}}(vertices, img_shape) + + slicer = _Slicer() + slicer._from_ptr((&cslicer)) #makes a copy (as before), but perhaps there is a way to do without? return slicer {{endfor}} @@ -984,7 +1074,7 @@ def to_blocks(input): raise ValueError("Input cannot be converted to blocks.") -def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dtypes, str col, str pers_backend): +def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dtypes, str col, str pers_backend, str filtration_container:_filtration_container_type): """ Given various parameters, returns the specific slicer type associated with them. """ @@ -992,12 +1082,19 @@ def get_matrix_slicer(bool is_vineyard, bool is_k_critical, dtype:type|_valid_dt pass {{for D in slicers}} {{if not D['IS_SIMPLICIAL']}} - elif is_vineyard == {{D['IS_VINE']}} and is_k_critical == {{D['IS_KCRITICAL']}} and np.dtype(dtype) is np.dtype({{D['PY_VALUE_TYPE']}}) and col.lower() == "{{D['COLUMN_TYPE']}}".lower() and "{{D['PERS_BACKEND_TYPE']}}".lower() == pers_backend.lower(): + elif ( + is_vineyard == {{D['IS_VINE']}} + and is_k_critical == {{D['IS_KCRITICAL']}} + and np.dtype(dtype) is np.dtype({{D['PY_VALUE_TYPE']}}) + and col.lower() == "{{D['COLUMN_TYPE']}}".lower() + and "{{D['PERS_BACKEND_TYPE']}}".lower() == pers_backend.lower() + and filtration_container.lower() == "{{D['SHORT_FILTRATION_TYPE']}}".lower() + ): return {{D['PYTHON_TYPE']}} {{endif}} {{endfor}} else: - raise ValueError(f"Unimplemented combo for {pers_backend} : {is_vineyard=}, {is_k_critical=}, {dtype=}, {col=}") + raise ValueError(f"Unimplemented combo for {pers_backend} : {is_vineyard=}, {is_k_critical=}, {dtype=}, {col=}, {filtration_container=}") diff --git a/multipers/tests/__init__.py b/multipers/tests/__init__.py index 43fd186e..bdf1d1af 100644 --- a/multipers/tests/__init__.py +++ b/multipers/tests/__init__.py @@ -24,10 +24,11 @@ def sort_sm(sms): def assert_sm_pair(sm1, sm2, exact=True, max_error=1e-3, reg=0.1, threshold=None): if not exact: from multipers.distances import sm_distance + if threshold is not None: _inf_value_fix = threshold - sm1[0][sm1[0] >threshold] = _inf_value_fix - sm2[0][sm2[0] >threshold] = _inf_value_fix + sm1[0][sm1[0] > threshold] = _inf_value_fix + sm2[0][sm2[0] > threshold] = _inf_value_fix d = sm_distance(sm1, sm2, reg=reg) assert d < max_error, f"Failed comparison:\n{sm1}\n{sm2},\n with distance {d}." @@ -45,7 +46,14 @@ def assert_sm(*args, exact=True, max_error=1e-5, reg=0.1, threshold=None): sms = tuple(args) for i in range(len(sms) - 1): print(i) - assert_sm_pair(sms[i], sms[i + 1], exact=exact, max_error=max_error, reg=reg, threshold=threshold) + assert_sm_pair( + sms[i], + sms[i + 1], + exact=exact, + max_error=max_error, + reg=reg, + threshold=threshold, + ) def random_st(npts=100, num_parameters=2, max_dim=2): diff --git a/multipers/torch/diff_grids.py b/multipers/torch/diff_grids.py index 3942f516..acc49a6f 100644 --- a/multipers/torch/diff_grids.py +++ b/multipers/torch/diff_grids.py @@ -37,7 +37,7 @@ def todense(grid: list[torch.Tensor]): def _exact_grid(filtration_values, r=None): - assert r is None + # assert r is None grid = tuple(_unique_any(f) for f in filtration_values) return grid diff --git a/multipers/vector_interface.pxd b/multipers/vector_interface.pxd new file mode 100644 index 00000000..f94f1c54 --- /dev/null +++ b/multipers/vector_interface.pxd @@ -0,0 +1,46 @@ + +from libcpp.utility cimport pair +from libc.stdint cimport intptr_t, uint16_t, uint32_t, int32_t, int64_t +from libcpp.vector cimport vector +from libcpp cimport bool, float + +cdef extern from "config.hpp": + cdef cppclass aida_config "aida::AIDA_config": + bool sort + bool exhaustive + bool brute_force + bool sort_output + bool compare_both + bool exhaustive_test + bool progress + bool save_base_change + bool turn_off_hom_optimisation + bool show_info + bool compare_hom + bool supress_col_sweep + bool alpha_hom + + +cdef extern from "aida_interface.hpp": + + cdef cppclass multipers_interface_input "aida::multipers_interface_input": + multipers_interface_input(const vector[pair[double,double]]&, const vector[pair[double,double]]&, const vector[vector[int]]&) except + nogil + multipers_interface_input() except + nogil + vector[pair[double,double]] col_degrees + vector[pair[double,double]] row_degrees + vector[vector[int]] matrix + + cdef cppclass multipers_interface_output "aida::multipers_interface_output": + multipers_interface_output() except + nogil + vector[multipers_interface_input] summands + # vector[pair[double,double]] col_degrees + # vector[pair[double,double]] row_degrees + # vector[vector[int]] matrix + + cdef cppclass AIDA_functor "aida::AIDA_functor": + AIDA_functor() except + nogil + multipers_interface_output multipers_interface(multipers_interface_input&) except + nogil + aida_config config + + + diff --git a/multipers/vector_interface.pyx b/multipers/vector_interface.pyx new file mode 100644 index 00000000..0fc91c6f --- /dev/null +++ b/multipers/vector_interface.pyx @@ -0,0 +1,92 @@ +import cython +from multipers.vector_interface cimport * +import numpy as np +import multipers as mp + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef vector[pair[double, double]] array_view_to_vect_pair(double[:, :] arr_view) noexcept nogil: + cdef int n = arr_view.shape[0] + cdef vector[pair[double, double]] result_vector + result_vector.resize(n) + for i in range(n): + result_vector[i] = pair[double, double](arr_view[i, 0], arr_view[i, 1]) + return result_vector + +def _aida(col_degrees, row_degrees, matrix): + cdef multipers_interface_input stuff = multipers_interface_input(col_degrees, row_degrees, matrix) + cdef AIDA_functor truc + truc.config.show_info = True + truc.config.sort_output = True + truc.config.sort = True + cdef multipers_interface_output stuff2 = truc.multipers_interface(stuff) + out = [] + for i in range(stuff2.summands.size()): + out.append((stuff2.summands[i].col_degrees, stuff2.summands[i].row_degrees, stuff2.summands[i].matrix)) + return out + +def aida(s, bool sort=True, bool verbose=False, bool progress = False): + if s.num_parameters != 2 or not s.is_minpres: + raise ValueError(f"AIDA is only compatible with 2-parameter minimal presentations. Got {s.num_parameters=} and {s.is_minpres=}.") + cdef bool is_squeezed = s.is_squeezed + + cdef int degree = s.minpres_degree + if sort: + s = s.to_colexical() + F = np.asarray(s.get_filtrations()) + D = s.get_dimensions() + cdef double[:,:] row_degree_ = np.asarray(F[D==degree], dtype = np.float64) + cdef double[:,:] col_degree_ = np.asarray(F[D==degree+1], dtype = np.float64) + cdef vector[pair[double,double]] row_degree = array_view_to_vect_pair(row_degree_) + cdef vector[pair[double,double]] col_degree = array_view_to_vect_pair(col_degree_) + i,j = np.searchsorted(D, [degree+1,degree+2]) + cdef vector[vector[int]] matrix = s.get_boundaries()[i:j] + + cdef AIDA_functor truc + cdef multipers_interface_input stuff + cdef multipers_interface_output stuff2 + with nogil: + truc.config.show_info = verbose + truc.config.sort_output = False + truc.config.sort = sort + truc.config.progress = progress + stuff = multipers_interface_input(col_degree, row_degree, matrix) + stuff2 = truc.multipers_interface(stuff) + out = [] + _Slicer = mp.Slicer(return_type_only=True, dtype=np.float64) + out = [_Slicer() for _ in range(stuff2.summands.size())] + dim_container_ = s.get_dimensions().copy() + cdef int32_t[:] dim_container = np.asarray(dim_container_, dtype=np.int32) + cdef list boundary_container + cdef vector[pair[double,double]] FR + cdef vector[pair[double,double]] FG + cdef vector[vector[int]] B + for i in range(stuff2.summands.size()): + FR = stuff2.summands[i].col_degrees + FG = stuff2.summands[i].row_degrees + B = stuff2.summands[i].matrix + + for j in range(FG.size()): + dim_container[j] = degree + for j in range(FG.size(),FG.size()+FR.size()): + dim_container[j] = degree +1 + + boundary_container = [[] for _ in range(FG.size())] + boundary_container.extend(B) + + if FR.size() == 0: + filtration_values = np.asarray(FG) + else: + filtration_values = np.concatenate([FG,FR], dtype=np.float64) + + s_summand = _Slicer( + boundary_container, + dim_container[:FG.size()+FR.size()], + filtration_values + ) + if s.is_squeezed: + s_summand.filtration_grid = s.filtration_grid + s_summand._clean_filtration_grid() + out[i] = s_summand + + return out diff --git a/setup.py b/setup.py index ccea9e69..1063c45b 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ -# import contextlib -import filecmp import os -import shutil import platform +import subprocess import sys +import filecmp +import shutil from pathlib import Path import numpy as np @@ -17,15 +17,7 @@ Options.fast_fail = True # Options.warning_errors = True -try: - os.mkdir("build") -except FileExistsError: - pass - -try: - os.mkdir("build/tmp") -except FileExistsError: - pass +os.makedirs("build/tmp", exist_ok=True) def was_modified(file): @@ -60,6 +52,9 @@ def process_tempita(fromfile): """ if not was_modified(fromfile) and not full_build: return + print("#-----------------------------------") + print(f"processing {fromfile}.") + print("#-----------------------------------") with open(fromfile, "r", encoding="utf-8") as f: template_content = f.read() @@ -81,9 +76,11 @@ def process_tempita(fromfile): "point_measure", "grids", "slicer", + "vector_interface", ] templated_cython_modules = [ + "filtrations.pxd", "filtration_conversions.pxd", "slicer.pxd", "mma_structures.pyx", @@ -91,18 +88,13 @@ def process_tempita(fromfile): "slicer.pyx", ] -## generates some parameter files (Tempita fails with python<3.12) +# generates some parameter files (Tempita fails with python<3.12) # TODO: see if there is a way to avoid _tempita_grid_gen.py or a nicer way to do it -os.system("python _tempita_grid_gen.py") +subprocess.run([sys.executable, "_tempita_grid_gen.py"], check=True) for mod in templated_cython_modules: process_tempita(f"multipers/{mod}.tp") -## Broken on mac -# n_jobs = 1 -# with contextlib.suppress(ImportError): -# import joblib -# n_jobs = joblib.cpu_count() cythonize_flags = { # "depfile": True, @@ -131,7 +123,7 @@ def process_tempita(fromfile): # When venv is not properly set, we have to add the current python path # removes lib / python3.x / site-packages -PYTHON_ENV_PATH = sys.prefix +PYTHON_ENV_PATH = Path(sys.prefix) cpp_dirs = [ "multipers/gudhi", @@ -140,47 +132,83 @@ def process_tempita(fromfile): # "multipers/multi_parameter_rank_invariant", # "multipers/tensor", np.get_include(), - PYTHON_ENV_PATH + "/include/", # Unix - PYTHON_ENV_PATH + "/Library/include/", # Windows + PYTHON_ENV_PATH / "include", # Unix + PYTHON_ENV_PATH / "Library" / "include", # Windows + "AIDA/src", + "AIDA/include", + "Persistence-Algebra/include", ] cpp_dirs = [str(Path(stuff).expanduser().resolve()) for stuff in cpp_dirs] library_dirs = [ - PYTHON_ENV_PATH + "/lib/", # Unix - PYTHON_ENV_PATH + "/Library/lib/", # Windows + PYTHON_ENV_PATH / "lib", # Unix + PYTHON_ENV_PATH / "Library" / "lib", # Windows ] library_dirs = [str(Path(stuff).expanduser().resolve()) for stuff in library_dirs] + +## AIDA stuff + +AIDA_PATHS = [ + Path("AIDA/src"), + Path("AIDA/include"), +] + +# Recursively collect all .cpp files from the AIDA directories +AIDA_CPP_SOURCES = [] +for p in AIDA_PATHS: + # Use Path.rglob('*.cpp') to recursively find all .cpp files + AIDA_CPP_SOURCES.extend([str(file) for file in p.rglob("*.cpp")]) + +print("AIDA files:") +print(AIDA_CPP_SOURCES) + print("Include dirs:") print(cpp_dirs) print("Library dirs:") print(library_dirs) + +def cpp_lib_deps(module): + if module == "vector_interface": + return ["boost_system", "boost_timer"] + else: + return ["tbb"] + + extensions = [ Extension( f"multipers.{module}", - sources=[ - f"multipers/{module}.pyx", - ], + sources=( + [ + f"multipers/{module}.pyx", + ] + + (AIDA_CPP_SOURCES if module == "vector_interface" else []) + ), language="c++", extra_compile_args=[ - "-O3", # -Ofast disables infinity values for filtration values + "-O3" + if platform.system() != "Windows" + else "/O2", # -Ofast disables infinity values for filtration values "-fassociative-math", "-funsafe-math-optimizations", + "-DGUDHI_USE_TBB", + "-DWITH_TBB=ON", # "-g", # "-march=native", - "/std:c++20" if platform.system() == "Windows" else "-std=c++20", + "-DNDEBUG" if platform.system() != "Windows" else "/DNDEBUG", + "-std=c++20" if platform.system() != "Windows" else "/std:c++20", # "-fno-aligned-new", # Uncomment this if you have trouble compiling on macos. "-Wall", "-Wextra" if platform.system() != "Windows" else "", # "-Werror" if platform.system() != "Windows" else "", ], - extra_link_args=[], ## mvec for python312 + extra_link_args=[], include_dirs=cpp_dirs, define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")], - libraries=["tbb"], + libraries=cpp_lib_deps(module), library_dirs=library_dirs, ) for module in cython_modules diff --git a/tests/test_aida.py b/tests/test_aida.py new file mode 100644 index 00000000..60b61cea --- /dev/null +++ b/tests/test_aida.py @@ -0,0 +1,46 @@ +import multipers as mp +from joblib import Parallel, delayed +from multipers.point_measure import add_sms +from multipers.vector_interface import aida +from multipers.tests import random_st +from multipers.tests import assert_sm +import pytest + +mpfree_flag = mp.io._check_available("mpfree") + + +def test_indecomposable(): + B = [[], [], [0, 1]] + D = [7, 7, 8] + F = [[0, 1], [1, 0], [2, 2]] + + # an indecomposable + s = mp.Slicer(return_type_only=True)(B, D, F) + s.minpres_degree = 7 + s = s.to_colexical() + s2 = aida(s) + assert len(s2) == 1 + s2 = s2[0].to_colexical() + assert s == s2 + + +@pytest.mark.skipif( + not mpfree_flag, + reason="Skipped external test as `mpfree` was not found.", +) +def test_equality(): + st = random_st() + degree = 1 + s = mp.Slicer(st).minpres(degree) + (sm1,) = mp.signed_measure(s, degree=degree, invariant="hilbert") + s_ = aida(s) + sms = Parallel(n_jobs=-1, backend="threading")( + delayed( + lambda x: mp.signed_measure( + x.minpres(degree), degree=degree, invariant="hilbert" + )[0] + )(x) + for x in s_ + ) + sm2 = add_sms(sms) + assert_sm(sm1, sm2, exact=False, max_error=1e-10, reg=0) diff --git a/tests/test_diff_helper.py b/tests/test_diff_helper.py index 5d50944e..5ed0fff4 100644 --- a/tests/test_diff_helper.py +++ b/tests/test_diff_helper.py @@ -27,9 +27,9 @@ def test_h1_rips_density(): for indices in sm_indices ] ) - assert np.array_equal( - reconstructed_measure, pts - ), "Reconstructed measure is not equal to original measure ?" + assert np.array_equal(reconstructed_measure, pts), ( + "Reconstructed measure is not equal to original measure ?" + ) def test_h0_rips_density(): @@ -46,9 +46,9 @@ def test_h0_rips_density(): (sm,) = mp.signed_measure(st, degree=0, plot=True, mass_default=None) pts, weights = sm _, unmappable_points = st.pts_to_indices(pts, simplices_dimensions=[1, 0]) - assert ( - pts[unmappable_points[:, 0]][:, 0] == 0 - ).all(), "Unmapped points of H0 have to be the nodes of the rips." + assert (pts[unmappable_points[:, 0]][:, 0] == 0).all(), ( + "Unmapped points of H0 have to be the nodes of the rips." + ) # def test_h1_rips_density_rank(): diff --git a/tests/test_mma.py b/tests/test_mma.py index 7c01997b..a152af45 100644 --- a/tests/test_mma.py +++ b/tests/test_mma.py @@ -19,8 +19,8 @@ def test_1(): def test_img(): simplextree = mp.SimplexTreeMulti(num_parameters=4) simplextree.insert([0], [1, 2, 3, 4]) - mod = mp.module_approximation(simplextree, - box=[[0, 0, 0, 0], [5, 5, 5, 5]], max_error=1.0 + mod = mp.module_approximation( + simplextree, box=[[0, 0, 0, 0], [5, 5, 5, 5]], max_error=1.0 ) img = mod.representation(resolution=6, kernel="linear") assert np.isclose(img[0, 1, 2, 3, 4], 0.5) @@ -92,3 +92,12 @@ def test_dump_load(): assert mod == _mod mod = mp.module_approximation(random_st()) assert _mod != mod + + +def test_swap_coord_hack(): + import multipers as mp + + x = mp.data.three_annulus(10, 10) + s = mp.filtrations.RipsCodensity(points=x, bandwidth=0.2) + mod = mp.module_approximation(s, direction=[1, 0], swap_box_coords=[1]) + assert mod.num_parameters == 2 diff --git a/tests/test_parallel.py b/tests/test_parallel.py index 263b0f71..236aa1bc 100644 --- a/tests/test_parallel.py +++ b/tests/test_parallel.py @@ -27,9 +27,37 @@ def io_fd_mpfree2(x): @pytest.mark.parametrize("backend", ["loky", "threading"]) def test_io_parallel(backend): x = mp.data.three_annulus(100, 50) - X = [x] * 100 + X = [x] * 15 ground_truth = io_fd_mpfree2(x) dgms = Parallel(n_jobs=-1, backend=backend)(delayed(io_fd_mpfree)(x) for x in X) for dgm in dgms: - assert_sm_pair(ground_truth, dgm, exact=False, reg=0, max_error= 1e-10) + assert_sm_pair(ground_truth, dgm, exact=False, reg=0, max_error=1e-12) + + +x = mp.data.three_annulus(50, 10) +st = mp.filtrations.RipsCodensity(points=x, bandwidth=0.1) +st = st.collapse_edges(-2) +st.expansion(2) + + +def get_sm_st(n_jobs=1, to_slicer=False, invariant="hilbert"): + if to_slicer: + return mp.signed_measure( + mp.Slicer(st), degree=1, n_jobs=n_jobs, invariant=invariant + )[0] + return mp.signed_measure(st, degree=1, n_jobs=n_jobs, invariant=invariant)[0] + + +@pytest.mark.parametrize("backend", ["loky", "threading"]) +@pytest.mark.parametrize("slicer", [False, True]) +@pytest.mark.parametrize("invariant", ["hilbert"]) +def test_st_sm_parallel(backend, slicer, invariant): + ground_truth = get_sm_st(n_jobs=1, to_slicer=slicer) + ground_truth2 = get_sm_st(n_jobs=1, to_slicer=not slicer) + assert_sm_pair(ground_truth, ground_truth2, reg=0, exact=False, max_error=1e-12) + sms = Parallel(n_jobs=-1, backend=backend)( + delayed(get_sm_st)(-1, slicer, invariant) for _ in range(15) + ) + for sm in sms: + assert_sm_pair(ground_truth, sm, exact=False, reg=0, max_error=1e-12) diff --git a/tests/test_python-cpp_conversion.py b/tests/test_python-cpp_conversion.py index 491befee..b7b0ac7a 100644 --- a/tests/test_python-cpp_conversion.py +++ b/tests/test_python-cpp_conversion.py @@ -17,9 +17,9 @@ def test_random_alpha_safe_conversion(): np.all([s in st_multi for s, f in st_gudhi.get_simplices()]) and st_gudhi.num_simplices() == st_multi.num_simplices ), "Simplices conversion failed." - assert np.all( - [f.shape[0] == num_parameter for _, f in st_multi.get_simplices()] - ), "Number of parameters is inconcistent" + assert np.all([f.shape[0] == num_parameter for _, f in st_multi.get_simplices()]), ( + "Number of parameters is inconcistent" + ) assert np.all( [np.isclose(st_multi.filtration(s)[0], f) for s, f in st_gudhi.get_simplices()] ), "Filtration values conversion failed." @@ -36,9 +36,9 @@ def test_random_alpha_unsafe_conversion(): np.all([s in st_multi for s, f in st_gudhi.get_simplices()]) and st_gudhi.num_simplices() == st_multi.num_simplices ), "Simplices conversion failed." - assert np.all( - [f.shape[0] == num_parameter for _, f in st_multi.get_simplices()] - ), "Number of parameters is inconcistent" + assert np.all([f.shape[0] == num_parameter for _, f in st_multi.get_simplices()]), ( + "Number of parameters is inconcistent" + ) assert np.all( [np.isclose(st_multi.filtration(s)[0], f) for s, f in st_gudhi.get_simplices()] ), "Filtration values conversion failed." @@ -55,9 +55,9 @@ def test_random_rips_safe_conversion(): np.all([s in st_multi for s, f in st_gudhi.get_simplices()]) and st_gudhi.num_simplices() == st_multi.num_simplices ), "Simplices conversion failed." - assert np.all( - [f.shape[0] == num_parameter for _, f in st_multi.get_simplices()] - ), "Number of parameters is inconcistent" + assert np.all([f.shape[0] == num_parameter for _, f in st_multi.get_simplices()]), ( + "Number of parameters is inconcistent" + ) assert np.all( [np.isclose(st_multi.filtration(s)[0], f) for s, f in st_gudhi.get_simplices()] ), "Filtration values conversion failed." @@ -74,9 +74,9 @@ def test_random_alpha_unsafe_conversion(): np.all([s in st_multi for s, f in st_gudhi.get_simplices()]) and st_gudhi.num_simplices() == st_multi.num_simplices ), "Simplices conversion failed." - assert np.all( - [f.shape[0] == num_parameter for _, f in st_multi.get_simplices()] - ), "Number of parameters is inconcistent" + assert np.all([f.shape[0] == num_parameter for _, f in st_multi.get_simplices()]), ( + "Number of parameters is inconcistent" + ) assert np.all( [np.isclose(st_multi.filtration(s)[0], f) for s, f in st_gudhi.get_simplices()] ), "Filtration values conversion failed." diff --git a/tests/test_signed_measure.py b/tests/test_signed_measure.py index b3700391..670ddaa2 100644 --- a/tests/test_signed_measure.py +++ b/tests/test_signed_measure.py @@ -118,9 +118,9 @@ def test_hook_decomposition(): ) sm_hook = rectangle_to_hook_minimal_signed_barcode(*sm_rank) pts_hook, w_hook = sm_hook - assert np.array_equal( - pts_hook, [[0, 0, 0, 1], [0, 0, 1, 0], [0, 0, 1, 1]] - ), pts_hook + assert np.array_equal(pts_hook, [[0, 0, 0, 1], [0, 0, 1, 0], [0, 0, 1, 1]]), ( + pts_hook + ) assert np.array_equal(w_hook, [1, 1, -1]), w_hook B = [ diff --git a/tests/test_simplextreemulti.py b/tests/test_simplextreemulti.py index c8b0537e..b9dafe1a 100644 --- a/tests/test_simplextreemulti.py +++ b/tests/test_simplextreemulti.py @@ -7,6 +7,7 @@ import multipers as mp from multipers.tests import assert_st_simplices, random_st +from multipers.simplex_tree_multi import available_dtype def test_1(): @@ -42,14 +43,8 @@ def test_3(): st.insert([0, 1], 1) st.insert([1], 0) # converts the simplextree into a multiparameter simplextree - for dtype in [np.int32, np.int64, np.float32, np.float64]: - try: - st_multi = mp.SimplexTreeMulti(st, num_parameters=4, dtype=dtype) - except KeyError: - import sys - - print(f"type {dtype} not compiled, skipping.", file=sys.stderr) - continue ## dtype not compiled + for dtype in available_dtype: + st_multi = mp.SimplexTreeMulti(st, num_parameters=4, dtype=dtype) minf = -np.inf if isinstance(dtype(1), np.floating) else np.iinfo(dtype).min it = [ (array([0, 1]), array([1.0, minf, minf, minf])), @@ -183,9 +178,9 @@ def test_serialize(): assert not stm == stm2 st1 = stm.project_on_line(parameter=0) stm = mp.SimplexTreeMulti(st1, num_parameters=3) - assert st1 == stm.project_on_line( - parameter=0 - ), "Gudhi<->Multipers conversion failed" + assert st1 == stm.project_on_line(parameter=0), ( + "Gudhi<->Multipers conversion failed" + ) @pytest.mark.skipif( @@ -193,7 +188,6 @@ def test_serialize(): reason="kcritical simplextree not compiled, skipping this test", ) def test_kcritical_batch_insert(): - st = mp.SimplexTreeMulti(num_parameters=2, kcritical=True, dtype=np.float64) vertices = [[0, 1]] diff --git a/tests/test_slicer.py b/tests/test_slicer.py index 8583fc7f..f6a8a726 100644 --- a/tests/test_slicer.py +++ b/tests/test_slicer.py @@ -40,9 +40,9 @@ def test_1(): it = s.persistence_on_line( [0, 0], [1, 1], ignore_infinite_filtration_values=False ) - assert ( - len(it) == 2 - ), "There are simplices of dim 0 and 1, but no pers ? got {}".format(len(it)) + assert len(it) == 2, ( + "There are simplices of dim 0 and 1, but no pers ? got {}".format(len(it)) + ) assert len(it[1]) == 0, "Pers of dim 1 is not empty ? got {}".format(it[1]) for x in it[0]: if np.any(np.asarray(x)): @@ -145,7 +145,7 @@ def test_representative_cycles(): {3, 6}, ] truc = [list(machin) for machin in truc] - slicer = mp.slicer._Slicer_Matrix0_vine_f64( + slicer = mp.slicer._ContiguousSlicer_Matrix0_vine_f64( truc, np.array([max(len(x) - 1, 0) for x in truc]), np.array([list(range(len(truc))), list(range(len(truc)))]).T, @@ -153,13 +153,13 @@ def test_representative_cycles(): slicer.compute_persistence(one_filtration=list(range(len(truc)))) cycles = slicer.get_representative_cycles() assert len(cycles) == 3, f"There should be 3 dimensions here, found {len(cycles)}" - assert ( - np.asarray(cycles[0]).shape[0] == 7 - ), f"Invalid number of 0-cycles, got {np.asarray(cycles[0]).size}" + assert len(cycles[0]) == 7, ( + f"Invalid number of 0-cycles, got {np.asarray(cycles[0]).size}" + ) for c in cycles[1]: - assert ( - np.unique(cycles[1][0], return_counts=True)[1] == 2 - ).all(), f"Found a non-cycle, {cycles[1][0]}" + assert (np.unique(cycles[1][0], return_counts=True)[1] == 2).all(), ( + f"Found a non-cycle, {cycles[1][0]}" + ) assert len(cycles[2]) == 0, "Found a 2-cycle, which should not exist" @@ -172,13 +172,13 @@ def test_pruning(): s2 = mp.Slicer(s, max_dim=2) assert s.get_dimensions()[-1] == 2, "Failed to prune dimension" st.prune_above_dimension(2) - assert ( - s.get_dimensions() == s2.get_dimensions() - ).all(), "pruned dimensions do not coincide" + assert (s.get_dimensions() == s2.get_dimensions()).all(), ( + "pruned dimensions do not coincide" + ) assert s.get_boundaries() == s2.get_boundaries(), "Boundaries changed" - assert ( - s.get_filtrations_values() == s2.get_filtrations_values() - ).all(), "Filtrations have changed" + assert (s.get_filtrations_values() == s2.get_filtrations_values()).all(), ( + "Filtrations have changed" + ) def test_scc():