Zigzag persistence part2 - #953
Conversation
… oscillating_rips
|
I still have to re-read trough the documentation to be sure that nothing was forgotten with the last changes, but otherwise the module should be ready to be reviewed. The unit tests were finally added and I completed the introduction of the oscillating rips filtrations. |
mglisse
left a comment
There was a problem hiding this comment.
Some early comments, or reminders to discuss some things, but I haven't really read the PR yet.
| </tr> | ||
| </table> | ||
|
|
||
| ### Oscillating Rips Filtrations |
There was a problem hiding this comment.
Do we want this directly on the main page, or in the Rips page (which already has sparse Rips for instance)?
There was a problem hiding this comment.
I don't have a strong opinion about it. But if not every variant is displayed on the main page, we should perhaps add a mention to them in the module description of the main page. Something like:
The (Vietoris-)Rips complex is a filtered simplicial complex where every tuple of points appears with its diameter as filtration value. Alternatively, it is defined by the cliques of a graph, usually representing a metric.
This complex can be built from a point cloud and a distance function, or from a distance matrix.
Implemented variants:
- Vietoris-Rips complex
- Sparse Rips complex
- Oscillating Rips filtration
They kind of go under otherwise...
| * If \f$ P \f$ is the set of points generating the rips filtration, \f$ P_i \f$ corresponds to the subset containing | ||
| * the \f$ i \f$ first points. So, at each forward inclusion, one vertex is added. | ||
| * | ||
| * The superscript of \f$ \mathcal{R} \f$ corresponds to the filtration values which will be associated to the cycles. |
There was a problem hiding this comment.
Maybe say that later? First we describe the zigzag. And once that's done, we can say that we (arbitrarily?) use this as "filtration value" for the output barcode.
I assume you chose epsilon because it is monotonous, unlike the max edge length parameter, while still being homogeneous to a distance, unlike the index?
There was a problem hiding this comment.
I was not the one who chose the epsilon values as filtration values, but yes, I think the reason was going in this direction. And it does make sense, so I didn't contested it.
I presented it like that, because I didn't wanted to confuse the reader with theoretical details, but describing directly how it was actually implemented in Gudhi (while still making clear how the filtration looks like). In particular, because there exists other implemented variants which don't chose the exact same parametrization (for example, Dionysus, even though I can't find its documentation anymore...).
But I could invert the positions of this paragraph with the one below and be more explicit about the fact that the choice of filtration is not part of the definition, just our choice.
| * information about the filtration "on the fly" to avoid loading the whole filtration at once. Information about the | ||
| * current barcode can be retrieved between any steps via callback methods. | ||
| * | ||
| * \subsection zigzagrips Oscillating Rips |
There was a problem hiding this comment.
Do we want it on this page, which is more about persistence, while it is more about the geometry of building a zigzag filtration?
I am asking because Rips_complex and Persistent_cohomology are in 2 clearly separated pages.
There was a problem hiding this comment.
Good question. I always saw it as an extension of the zigzag module, so I found it natural to put it there. But when you think about it without this point of view, the placement is a bit weird indeed. We could put it together with the Rips complex like you suggested above.
But then, should I also move the actual code ? For now the import of the ranges is via #include <gudhi/Zigzag_persistence/oscillating_rips_simplex_ranges.h> and only the helper method is imported directly with #include <gudhi/Oscillating_rips_persistence.h>.
| * Both multipliers have to be specified by the user. | ||
| * | ||
| * The construction is based on two types of classes: | ||
| * - @ref Oscillating_rips_edge_iterator_range and @ref Oscillating_rips_edge_vector_range_constructor computes the |
There was a problem hiding this comment.
Why not shorten Oscillating_rips_edge_iterator_range to Oscillating_rips_edge_range? IIUC there is a range, begin on it returns Oscillating_rips_edge_iterator, an iterator is the thing that iterates on the elements of a range, everything works?
There was a problem hiding this comment.
It was just to contrast it with the vector version, i.e., to highlight that iterator_range vector_range. Same idea goes for your question below "Isn't range_ redundant in the name?".
But I am not super happy about my choice of names. So, any suggestion is welcome. It would just be nice if the difference between the two classes remain clear from the name. If possible.
| * use @ref Identity_edge_modifier. Default value: @ref Identity_edge_modifier. | ||
| */ | ||
| template <typename Filtration_value, class EdgeModifier = Identity_edge_modifier> | ||
| class Oscillating_rips_edge_vector_range_constructor |
There was a problem hiding this comment.
Isn't range_ redundant in the name?
| * - @ref Oscillating_rips_edge_iterator_range and @ref Oscillating_rips_edge_vector_range_constructor computes the | ||
| * range of inserted and removed vertices and edges in the filtration based on the elements descipted above. |
There was a problem hiding this comment.
Is it explained somewhere that one stores the whole filtration in a vector while the other computes the edges lazily as you iterate on it?
There was a problem hiding this comment.
It is explained in the description of the classes themselves. But I am okay with repeating it here.
Co-authored-by: Marc Glisse <marc.glisse@inria.fr>
Co-authored-by: Marc Glisse <marc.glisse@inria.fr>
Co-authored-by: Marc Glisse <marc.glisse@inria.fr>
mglisse
left a comment
There was a problem hiding this comment.
Random comments. Some may be irrelevant because I haven't read the full PR yet, but since I am going to disappear for a couple days again, I might as well post them.
| #ifndef CONCEPT_ZZ_POINT_RANGE_H_ | ||
| #define CONCEPT_ZZ_POINT_RANGE_H_ | ||
|
|
||
| /** @file PointRange.h |
There was a problem hiding this comment.
Could you remind me what this is for? I think doxygen knows the name of the file it is processing, and the user shouldn't care about the name of the file where the concept is documented.
There was a problem hiding this comment.
It helps Doxygen to index the files better and avoids a few @ref problems. It is one of those Doxygen properties, where you don't really need it most of the time, but at some seemly random point, it will refuse referencing a method because it was missing.
It also adds a description in the file list on the website.
| * @param f Value to modify. | ||
| * @return The modified value of @p f. | ||
| */ | ||
| static Filtration_value apply_modifier(Filtration_value f); |
There was a problem hiding this comment.
static means we won't be able to use an EdgeModifier that stores data (say PowerEdgeModifier that has a field storing the desired exponent at runtime). If we don't expect to use anything other than identity or sqrt, I guess that's ok.
There was a problem hiding this comment.
True. But the way it is implemented for now, the user never passes an instance of Edge_modifier to a method, only the type, from which we call Edge_modifier::apply_inverse_modifier etc. We would need to change that.
If it becomes useful, we can still add it then, I think. With an optional argument for example which won't break retro-compatibility.
|
|
||
| private: | ||
| const std::vector<Filtration_value>* epsilonValues_; /**< Epsilon values. */ | ||
| const std::vector<std::vector<std::pair<int, Filtration_value> > >* distanceMatrix_; /**< Distance matrix. */ |
There was a problem hiding this comment.
IIUC this is a lower triangular matrix without diagonal (m[i][j] only valid if j<i), maybe add that as a comment?
If we wanted to, we could make it a flat vector, element (i,j) would have index i*(i-1)/2+j or a similar formula, but it may not be interesting, and it is private so can be changed later anyway.
There was a problem hiding this comment.
I didn't wanted to modify too much the initial implementation, but making the matrix flat could be a good idea. But as you say, it is private anyway as Oscillating_rips_edge_iterator_base is not really supposed to be used externally, so we can look at it later. I kept the documentation of this class public mainly to keep references to it possible, but perhaps I should just remove them?
| VECTOR, /**< The edges are computed all at once and stored in a vector. */ | ||
| BOOST_RANGE /**< The edges are computed one by one at each increment of the | ||
| range iterator and therefore not stored. */ |
There was a problem hiding this comment.
I don't feel that BOOST_RANGE really conveys what this option does (I could easily wrap a vector in a boost range).
eager vs lazy?
precomputed/stored vs generated?
I don't have a very good suggestion...
There was a problem hiding this comment.
Changed it to ALL_AT_ONCE and ONE_BY_ONE. A bit ugly, but better than before, I guess? What do you think?
Follow up of PR #917 with the addition of the oscillating Rips iterator.
The PR is still a draft as unit tests are still missing (and the doc main page could be better). I want to ask a few things @ClementMaria when he comes back before doing them.
As this depends of #917 which itself depends on #669 there is still enough to do before then...