pkg: remove version from include/libstick-0.1/
[libstick.git] / include / libstick / persistence.h
diff --git a/include/libstick/persistence.h b/include/libstick/persistence.h
new file mode 100644 (file)
index 0000000..b9fc080
--- /dev/null
@@ -0,0 +1,336 @@
+#ifndef persistence_h_aPeinoXeiFeethuz
+#define persistence_h_aPeinoXeiFeethuz
+
+#include <iostream>
+#include <ostream>
+#include <vector>
+#include <cassert>
+
+#include "simplicialcomplex.h"
+#include "booleanmatrix.h"
+
+
+namespace libstick {
+
+
+/** Persistence computers various persistent homology information on a
+ * simplex_order of a simplicial_complex. */
+template<int MAXDIM, class IT, class VT>
+class persistence {
+
+    public:
+        typedef simplicial_complex<MAXDIM, IT, VT> scomplex;
+        typedef typename scomplex::simplex_order simplex_order;
+        typedef boolean_colmatrix<IT> boundary_matrix;
+        typedef boolean_colmatrix<IT> transformation_matrix;
+        typedef std::vector<IT> lowestones_matrix;
+
+        /** A point in a persistence diagram comprises birth- and death-index.
+         * These are the indices of the simplices of 'order' that give birth
+         * and death to the class. */
+        struct diagram_point {
+            /** 'birth'-th simplex in 'order' gave birth to this cycle. */
+            IT birth;
+            /** 'death'-th simplex in 'order' killed this cycle. If 'death' is
+             * zero, the cycle never dies. Otherwise, 'death' is always larger
+             * than 'birth'. */
+            IT death;
+        };
+
+        /** Save the births and deaths of simplices of a fixed dimension, say p. */
+        struct diagram {
+            /** Saves the sequence of birth/death-points of p-simplices, sorted
+             * by birth-index. If death-index is zero, the corresponding
+             * homology class never dies. */
+            std::vector<diagram_point> births;
+
+
+            /** Gives the p-th Betti number, i.e., the number of immortal
+             * p-dimensional homology classes. */
+            size_t betti() const {
+                size_t b = 0;
+
+                for (unsigned i=0; i < births.size(); ++i)
+                    if (births[i].death == 0)
+                        b++;
+
+                return b;
+            }
+
+            /** Gives the number of p-dimensional homology classes that are
+             * born by simplex 'from' or earlier and die after simplex 'to' is
+             * inserted. */
+            size_t persistent_betti(IT from, IT to) const {
+                assert(from <= to);
+                size_t b = 0;
+
+                for (unsigned i=0; i < births.size(); ++i) {
+                    // All following simplices are born too late.
+                    if (births[i].birth > from)
+                        break;
+                    if (births[i].death > to || births[i].death == 0)
+                        b++;
+                }
+
+                return b;
+            }
+        };
+
+    public:
+        /** Create a new peristence object on the given simplex_order */
+        persistence(const simplex_order &order) :
+            order(order),
+            bm(0),
+            rbm(0),
+            lowestones(0),
+            tm(0)
+            {
+            reset();
+        }
+
+        /** Reset all results gained from 'order'. */
+        void reset() {
+            done_matrices = false;
+            done_diagrams = false;
+        }
+
+        /** Get simplicial order of this persistence object. */
+        const simplex_order& get_order() const {
+            return order;
+        }
+
+        /** Get boundary matrix 'bm' of 'order', compute reduces boundary
+         * matrix 'rbm', and the transformation matrix 'tm' such that rbm = bm *
+         * tm. */
+        void compute_matrices() {
+            if (done_matrices)
+                return;
+            done_matrices = true;
+
+            bm = order.get_boundary_matrix();
+            rbm = bm;
+            tm = create_unit_matrix<transformation_matrix>(bm.width());
+            lowestones = lowestones_matrix(bm.width());
+
+            // Make every column reduced, i.e., it is a zero-column or contains only one 1.
+            for (unsigned c=0; c < rbm.width(); ++c) {
+                //if (c % 100 == 0)
+                //std::cout << "c = " << c << " (" << (100.0*float(c)/rbm.width()) << " %)" << std::endl;
+                // Reduce as long as we need to reduce
+                while (!rbm.get_column(c).isempty()) {
+                    // (r, c) is the lowest one of col
+                    const IT r = rbm.get_column(c).back();
+                    // This column contains its lowest one on the same row
+                    const IT c2 = lowestones[r];
+                    assert(c2 < c);
+
+                    // There is no valid c2, hence we have completely reduced
+                    if (c2 == 0)
+                        break;
+                    // Reduce col by column c2
+                    else {
+                        assert(rbm.get(r, c));
+                        rbm.add_column(c, rbm.get_column(c2));
+                        tm.add_column(c, tm.get_column(c2));
+                        assert(!rbm.get(r, c));
+                    }
+                }
+
+                // A lowest one remained, recall it
+                if (!rbm.get_column(c).isempty()) {
+                    const IT r = rbm.get_column(c).back();
+                    assert(lowestones[r] == 0);
+                    assert(r < c);
+                    lowestones[r] = c;
+                }
+            }
+        }
+
+        /** Get the lowest-one matrix of 'order'. */
+        const lowestones_matrix& get_lowestones_matrix() const {
+            assert(done_matrices);
+            return lowestones;
+        }
+
+        /** Get the boundary matrix of 'order'. */
+        const boundary_matrix& get_boundary_matrix() const {
+            assert(done_matrices);
+            return bm;
+        }
+
+        /** Get the reduced boundary matrix of 'order'. */
+        const boundary_matrix& get_reduced_boundary_matrix() const {
+            assert(done_matrices);
+            return rbm;
+        }
+
+        /** Get the transformation matrix of 'order', which transforms the
+         * boundary matrix to the reduced boundary matrix, when multiplied from
+         * the right. */
+        const transformation_matrix& get_transformation_matrix() const {
+            assert(done_matrices);
+            return tm;
+        }
+
+        /** Print the sequence of births and deaths of cycles (homology classes). */
+        void interpret_reduction(std::ostream &os) const {
+            assert(done_matrices);
+
+            for (unsigned c=0; c < bm.width(); ++c) {
+                os << c << ". inserting ";
+
+                switch (bm.get_column(c).size()) {
+                    case 0:
+                        os << "dummy vertex of dim -1";
+                        break;
+                    case 1:
+                        os << "vertex";
+                        break;
+                    case 2:
+                        os << "edge";
+                        break;
+                    case 3:
+                        os << "triangle";
+                        break;
+                    case 4:
+                        os << "tetrahedron";
+                        break;
+                    default:
+                        os << "simplex";
+                        break;
+                }
+
+                os << " \t\tvalue: " << order.get_simplex(c).value << std::endl;
+                os << "  boundary: " << bm.get_column(c) << std::endl;
+
+                typename boolean_colmatrix<IT>::column_type col = rbm.get_column(c);
+                if (col.isempty()) {
+                    os << "  \033[1;32mbirth\033[0;m of a cycle: " << tm.get_column(c) << std::endl;
+                } else {
+                    os << "  \033[1;31mdeath\033[0;m of a cycle: " << rbm.get_column(c) << std::endl;
+                    os << "  boundary of: " << tm.get_column(c) << std::endl;
+                }
+                os << std::endl;
+            }
+        }
+
+        /** Get the 'dim'-dimensional peristence diagram. */
+        const diagram& get_persistence_diagram(int dim) const{
+            assert(-1 <= dim);
+            assert(dim <= MAXDIM);
+            assert(done_diagrams);
+
+            return diagrams[dim+1];
+        }
+
+        /** Compute persistence diagrams */
+        void compute_diagrams() {
+            if (done_diagrams)
+                return;
+            done_diagrams = true;
+
+            compute_matrices();
+
+#ifndef NDEBUG
+            std::set<IT> deaths, births;
+#endif
+            //Clear all diagrams
+            for (int d=0; d < MAXDIM + 2; ++d)
+                diagrams[d] = diagram();
+
+            for (unsigned c=0; c < lowestones.size(); ++c) {
+                // A cycle was born
+                if (rbm.get_column(c).isempty()) {
+                    const int dim = order.get_simplex(c).dim;
+
+                    // Create a diagram point
+                    diagram_point p;
+                    p.birth = c;
+                    p.death = 0;
+                    // If the class actually dies, get the index of the killing simplex
+                    if (lowestones[c] > 0)
+                        p.death = lowestones[c];
+
+                    _get_persistence_diagram(dim).births.push_back(p);
+#ifndef NDEBUG
+                    assert(births.count(p.birth) == 0);
+                    assert(p.death == 0 || deaths.count(p.death) == 0);
+                    births.insert(p.birth);
+                    deaths.insert(p.death);
+#endif
+                }
+            }
+#ifndef NDEBUG
+            // Shakespeare principle: A simplex either gives birth or kills, be
+            // or not be, tertium non datur.
+            for (unsigned c=0; c < lowestones.size(); ++c) {
+                // A class died with c
+                if (!rbm.get_column(c).isempty())
+                    assert(c == 0 || deaths.count(c) > 0);
+                else
+                    assert(births.count(c) > 0);
+            }
+#endif
+        }
+
+        /** Print birth and death information in the persistence diagrams. */
+        void interpret_persistence(std::ostream &os) const {
+            assert(done_diagrams);
+
+            for (int d=-1; d <= MAXDIM; ++d) {
+                os << "Dimension " << d << std::endl;
+                const diagram &dia = get_persistence_diagram(d);
+
+                for (unsigned i=0; i < dia.births.size(); ++i) {
+                    const IT birth = dia.births[i].birth;
+                    const IT death = dia.births[i].death;
+
+                    os << "  ";
+                    if (death > 0) {
+                        const scomplex& c = get_order().get_complex();
+                        const VT pers = c.simplices[death].value - c.simplices[birth].value;
+                        os << birth << "\033[1;31m -> " << death << "\033[0;m" << " \tpers: " << pers;
+                    } else {
+                        os << "\033[1;32m" << birth << "\033[0;m" << " \t\t[essential]";
+                    }
+                    os << std::endl;
+                }
+            }
+        }
+
+    private:
+
+        diagram& _get_persistence_diagram(int dim) {
+            assert(-1 <= dim);
+            assert(dim <= MAXDIM);
+            assert(done_diagrams);
+
+            return diagrams[dim+1];
+        }
+
+        /** The underlying simplex order of this diagram. */
+        const simplex_order &order;
+
+        bool done_matrices;
+        bool done_diagrams;
+
+        /** The boundary matrix of 'order' */
+        boundary_matrix bm;
+        /** The reduced boundary matrix of 'order' */
+        boundary_matrix rbm;
+        /** Only the lowest ones per column of the reduced boundary matrix.
+         * lowestones[i] contains the column-index at which the lowest one at
+         * row i appears, and zero if no such column exists. */
+        lowestones_matrix lowestones;
+        /** The transformation matrix that transforms bm into rbm, i.e. rbm = bm * tm. */
+        transformation_matrix tm;
+
+        /** The container for all p-dimensional diagrams. The p-th diagram is
+         * save at index p+1, with -1 <= p <= MAXDIM. */
+        diagram diagrams[MAXDIM+2];
+};
+
+
+} // namespace libstick
+
+#endif