New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Curvature implementation #1606
Curvature implementation #1606
Conversation
@Jules-Deschamps Looking good! You also need to modify:https://github.com/geomstats/geomstats/blob/master/tests/data/connection_data.py
You can run |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM when test's infrastructure problem is solved.
@@ -453,6 +453,25 @@ def normal_basis(self, basis, base_point=None): | |||
|
|||
return gs.einsum("i, ikl->ikl", 1.0 / gs.sqrt(norms), basis) | |||
|
|||
def covariant_riemannian_tensor(self, base_point): | |||
r"""Compute purely covariant version of Riemannian tensor at base_point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, could you also add the formula for it, i.e. R_{ijkl} = g_{ll'}R_{ijk}^l' ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes will do! As for the testing thing I thought I had resolved this but turns out I haven't yes, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @Jules-Deschamps.
I'm just reviewing Connection.riemann_tensor
now. Can you please take a look to my comments.
I'll come back to this PR soon.
geomstats/geometry/connection.py
Outdated
R_ijk^l = riemann_tensor[i,j,k,l] | ||
Riemannian tensor curvature. | ||
""" | ||
base_point = gs.to_ndarray(base_point, to_ndim=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work when base_point
is a matrix?
Can we somehow avoid its use? Because we create a new array every time we do it. christoffels
should work with a base point or multiple base points, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I was using it to know the number of points in base_point (to be able to permute the axes using gs.transpose), but if gs.move_axis (which I did not know existed) works I will get rid of these lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But if base_point
is a matrix (representing only a point in the manifold), you will get wrong information, no?
Your branch is too far behind, that's why you cannot find moveaxis
. Please merge master after pulling upstream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works apparently because there is a squeeze in christoffels
. So for base_point
of shape (1,dim), christoffels
will return shape (dim, dim, dim) and jacobian_christoffels
(dim, dim, dim, dim) because it is defined with a squeeze. So these lines are really just to get the number of points in base_point
. I hope this is what you are talking about. This is a minimum reproducible example showing it works for several types of base_point
(and namely a matrix representing only a point in the manifold).
import geomstats.backend as gs
from geomstats.information_geometry.dirichlet import DirichletDistributions
space = DirichletDistributions(3)
point_1 = space.random_point()
point_2 = gs.array([[1.0, 1.0, 1.0]])
point_3 = gs.array([[1.0, 2.0, 2.0], [1.0, 2.0, 1.0]])
print(space.metric.riemann_tensor(point_1))
print(space.metric.riemann_tensor(point_2))
print(space.metric.riemann_tensor(point_3))
Ok I will merge thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So turns out I am keeping base_point = gs.to_ndarray(base_point, to_ndim=2)
in my last commit also because I want to compute the jacobian christoffels for each of the points in base_point
, which is not possible otherwise (or so far I haven't found anything).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean here is related with #1607: riemann_tensor
, the way it is coded now, will not work for manifolds whose default_point_type
is matrix
(in the same way christoffels
doesn't).
I may be missing something in terms of concepts, but shouldn't everything also work for that case? (otherwise, as I told in #1607, we need to reconsider the class where to put this code).
base_point = gs.to_ndarray(base_point, to_ndim=2)
can't work in the general case because you need to consider both point_types (matrix and vector). You need to do something like:
if self.default_point_type == 'vector':
n_points = base_point.shape[0] if len(base_point.shape) > 1 else 1
else:
n_points = base_point.shape[0] if len(base_points.shape > 2 else 1
or, even better:
from vectorization import get_n_points
n_points = get_n_points(base_point, self.default_point_type)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great addition 🎉 ! Just a few comments on the docstrings. Otherwise, it would be nice to have tests that check known values for the sectional curvature: for the hyperbolic space, sphere, and e.g. beta distributions.
geomstats/geometry/connection.py
Outdated
return curvature | ||
|
||
def riemann_tensor(self, base_point): | ||
r"""Compute Riemannian tensor at base_point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add in the docstring somewhere that the returned R_{ijk}^l
verifies R(X_i, X_j)X_k = \sum_{l=1}^n R_{ijk}^l X_l
where X_i
is i-th basis vector of the tangent space.
geomstats/geometry/connection.py
Outdated
|
||
def scalar_curvature(self, base_point): | ||
r"""Compute scalar curvature at base_point. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same: add formula of the scalar curvature in the docstring ?
@@ -453,6 +453,25 @@ def normal_basis(self, basis, base_point=None): | |||
|
|||
return gs.einsum("i, ikl->ikl", 1.0 / gs.sqrt(norms), basis) | |||
|
|||
def covariant_riemannian_tensor(self, base_point): | |||
r"""Compute purely covariant version of Riemannian tensor at base_point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be clarified by a formula. I suppose this is R_{ijks} = <R(X_i,X_j)X_k,X_s>=\sum_{l=1}^n R_{ijk}^lg_{ls}
? If so, it should be mentioned in the docstring, with X_i is the i-th basis vector of the tangent space
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. I just checked because the tests were wrong but the convention used in the literature (or at least on wikipedia) is VERY weird (R_{ijkl} = <R(x_j, x_k)x_i, x_l>
) so I think the previous successful tests were just a fluke. I will make a commit as soon as possible with your feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You must beware that the sign convention taken in Wikipedia for the Riemann curvature tensor (https://en.wikipedia.org/wiki/Riemann_curvature_tensor) is not the same as the one taken in geomstats (see this docstring) and this has effects on the rest of the definitions. So all definitions should be taken from the same reference book, and not Wikipedia.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @alebrigant !! @Jules-Deschamps , you can even put a mention in all the docstrings that we do not use the same convention as in wikipedia, and therefore there are differences. Otherwise this might confuse a lot of people.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at all the formulae, I'm not completely sure that they are all compatible, i.e. take the same conventions. The only way to avoid mistakes in my view, is to pick a reference, and to take all formulas from this reference. This reference should be added to all docstrings.
metric = self.metric_matrix(base_point) | ||
covariant_tensor = gs.einsum("...ij, ...klmj->...iklm", metric, riemann_tensor) | ||
return covariant_tensor | ||
|
||
def sectional_curvature(self, tangent_vec_a, tangent_vec_b, base_point=None): | ||
r"""Compute the sectional curvature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The wikipedia reference in the docstring should be removed because the convention is not the same one. Instead, one can put e.g. do Carmo, or some other book where the convention is the same.
I have added modifications following your feedbacks and have tested for dirichlet distributions (the sectional curvature is indeed everywhere negative), but for hyperbolic spaces it is more complicated because sectional and scalar curvatures require Also, I was wondering where I should add the tests and data generators so that I don't have to rewrite all the tests for every manifold? For example for dirichlet distributions, in
|
Fantastic, thanks for addressing the comments. There are still some testing issues, which you can access by clicking on "details" in the github workflows above, or e.g. here:
Yes, I believe we have to have this bit of redundant code for now:
but @LPereira95 was working on removing it so that you do not have to copy-paste it like this, in the future. @LPereira95 do you confirm? |
To test sectional curvature more precisely than just the sign, you can check that for beta distributions, at any point
where
See Appendix in https://www.sciencedirect.com/science/article/pii/S2405896321005851?via%3Dihub. |
Thanks again! I don't really understand how to address the issues pointed out by pytest because they are the same for every manifold (the data generation methods are not implemented without the '_' underscore whent they inherit from the tests so they don't know with what data to run the tests). This is not only for the bianchi identity but for all the new tests I have added in As for the testing of the sectional curvature on the Beta manifold, it works perfect! I have added the associated test. |
For the tests, I suggest for the moment that we add a skip to them (which we unskip as soon as I merge the tests PR). Nevertheless, notice that most of the metrics do not have We still need to go over some parts of the new code (see above) before merging, as it may not be working for several cases. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the changes ! I still recommend further checks of the docstrings, to avoid all mistakes. See the detailed comments.
geomstats/geometry/connection.py
Outdated
@@ -443,6 +443,9 @@ def curvature(self, tangent_vec_a, tangent_vec_b, tangent_vec_c, base_point): | |||
the curvature is defined by :math:`R(X,Y)Z = \nabla_{[X,Y]}Z | |||
- \nabla_X\nabla_Y Z + \nabla_Y\nabla_X Z`. | |||
|
|||
Convention used in the literature is: | |||
R_{ijkl} = <R(x_j, x_k)x_i, x_l> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not appear here because we only have a connection and no Riemannian metric. However it is a good addition to RiemannianMetric
.
geomstats/geometry/connection.py
Outdated
------- | ||
riemann_curvature : array-like, shape=[..., {dim, [n, m]}, {dim, [n, m]}, | ||
{dim, [n, m]}, {dim, [n, m]}] | ||
R_{ijk}^l = riemann_tensor[...,i,j,k,l] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, the convention used should be specified: what is the definition of R_{ijk}^l
?
If x_i
denotes local coordinates and X_i= \partial / \partial x_i
the i-th basis vector of the tangent space,
is the convention do Carmo's, where R(X_i, X_j)X_k = R_{ijk}^l X_l
, which means R_{ijk}^l = dx^l(R(X_i, X_j)X_k)
or is it Wikipedia's convention, where R_{ijk}^l = dx^l(R(X_j, X_k)X_i)
?
def sectional_curvature(self, tangent_vec_a, tangent_vec_b, base_point=None): | ||
r"""Compute the sectional curvature. | ||
|
||
For two orthonormal tangent vectors :math:`x,y` at a base point, | ||
the sectional curvature is defined by :math:`<R(x, y)x, y> = | ||
<R_x(y), y>`. For non-orthonormal vectors vectors, it is | ||
<R_x(y), y>` (see do Carmo). For non-orthonormal vectors vectors, it is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean <R(x, y)x, y>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was from before me (sectional_curvature
was already implemented but not curvature
) but I think it is yes. I will unify the conventions if there are some incompatibilities.
@@ -453,6 +453,25 @@ def normal_basis(self, basis, base_point=None): | |||
|
|||
return gs.einsum("i, ikl->ikl", 1.0 / gs.sqrt(norms), basis) | |||
|
|||
def covariant_riemannian_tensor(self, base_point): | |||
r"""Compute purely covariant version of Riemannian tensor at base_point. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at all the formulae, I'm not completely sure that they are all compatible, i.e. take the same conventions. The only way to avoid mistakes in my view, is to pick a reference, and to take all formulas from this reference. This reference should be added to all docstrings.
Remove upper version limit on torch
…t data and pytorch backend code
Fix expm due to changes in scipy.linalg.expm
Remove greetings GitHub workflow that often fails
Fix bug in the autodiff of pullback metric matrix
@Jules-Deschamps @alebrigant I'm tackling this now |
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
Add curvature tensors - Follow-up from PR #1606
Implementation of methods for metric and connection, so that riemannian metrics can return :
Tests on shapes of curvature tensors symmetries of riemannian tensor on test files (geometry_test_cases and data_generation), and on negativity of sectional curvature of dirichlet distributions (on another script file, I did not know if I should add it in the test file).