Skip to content

Adjust injectivity radii for (inverse) retractions on the Sphere (#900) - #915

Draft
sblelong wants to merge 12 commits into
JuliaManifolds:masterfrom
sblelong:injectivity-radii
Draft

Adjust injectivity radii for (inverse) retractions on the Sphere (#900)#915
sblelong wants to merge 12 commits into
JuliaManifolds:masterfrom
sblelong:injectivity-radii

Conversation

@sblelong

@sblelong sblelong commented Jul 6, 2026

Copy link
Copy Markdown

Hi!

Here's a first attempt to address the issue raised in #900 about injectivity radii for retractions and their inverse.

I tried to follow the unit testing pattern on the sphere but I'm requesting approval before proceeding with tests for the bijectivity_radius.

@kellertuer kellertuer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the start. Here are some first preliminary comments.

The test failure on CI does seem more related too SciML destroying something, so for comments on that we have to wait for that to work again (I wrote on their channel on slack)

Comment thread src/manifolds/Sphere.jl
end

_doc_injectivity_radius_sphere = raw"""
injectivity_radius(M::AbstractSphere[, p, ::ExponentialRetraction])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We can not do this.
If you replace ExponentialRetraction here with the Stabilized one, the function is now undefined for ExcponentialRetraction. Anyone relying on that because they use it (it is the standard even!) has their code breaking.
One thing we should always avoid is breaking other peoples code. Always under all circumstances. In 6 years (since we started 2019) we had 10 reasons to do so and we did also those only very carefully. So please do not delete the old variant.

You can for sure add a copy that does this for the stabilised one but I would even argue the stablized one just has a numerical check around so it could even just call infectivity radius on its inner retraction.

Comment thread src/manifolds/Sphere.jl

Return the injectivity radius for the [`ProjectionRetraction`](@extref `ManifoldsBase.ProjectionRetraction`) on the
[`AbstractSphere`](@ref), which is globally ``\frac{π}{2}``.
[`AbstractSphere`](@ref), which is globally ``\infty``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are here we should document this much more precise, what is injective here, we had a long discussion so lets also be precise here.

Comment thread src/manifolds/Sphere.jl
function injectivity_radius(M::AbstractSphere, p, m::AbstractRetractionMethod)
return _injectivity_radius(M, p, m)
end
_injectivity_radius(::AbstractSphere, ::ExponentialRetraction) = π

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this definition here is what is actually breaking. Let's not do that, but as mentioned define the stabilised one to return the one of the retraction it stabilises .

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does make much more sense. I will keep it Manifolds.jl so we can run tests before moving it to ManifoldsBase.jl where stabilized retractions are defined.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is our usual path. We introduce new features here in Manifolds.jl and once we notice they are good – well-designed and tested “in the real world”, we move them to ManifoldsBase

Comment thread src/manifolds/Sphere.jl
end

function _injectivity_radius(M::AbstractSphere, m::LogarithmicInverseRetraction)
return injectivity_radius(M, StabilizedRetraction())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No!
(a) Log is the mathematically most important thing, so that one should have the number it returns defined specifically
(b) the stabilised one is just a numerical thing
(c) why are you passing from an inverse retraction to a retraction? That looks at least misleading.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(c) I thought this was the right way to implement your suggestion in this message: the injectivity radius for log calls the one for exp. But I agree this is misleading, I will change that.

Comment thread src/manifolds/Sphere.jl
bijectivity_radius(::AbstractSphere, p, ::Union{AbstractRetractionMethod, AbstractInverseRetractionMethod})

function bijectivity_radius(M::AbstractSphere, m::Union{AbstractRetractionMethod, AbstractInverseRetractionMethod})
if m ∈ (StabilizedRetraction(), StabilizedInverseRetraction())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very strange and will often not yield what you want
(a) you compare instances. Even if someone comes along with an instance of the type, the instance might be a different one
(b) if you want to go for if you should check types, so that typeof(m) === StabilizedRetraction. The usually best way is – as all examples in the code before – to do that with dispatch.
(c) also here anything stabilised (since it is a computer/implementation/stability thing) should “pass” to the inner retraction (or inverse retraction) it stores.

Comment thread src/Manifolds.jl
SoftmaxRetraction,
SoftmaxInverseRetraction,
StabilizedRetraction,
StabilizedInverseRetraction,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not defined in ManifoldsBase? How can you import it from there then?

:InvalidVectors => [p],
:NormalVectors => [V],
:Covectors => [ξ],
:RetractionMethods => [ExponentialRetraction(), ProjectionRetraction()],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sane as before – no. The sphere should absolutely be tested on the ExponentialRetraction. Stabilisation is something extra – and exp is still the default. If you want to test it, add it at the end.

Comment thread NEWS.md

* `injectivity_radius(M, p, m)` and `injectivity_radius(M, m)` where `m` is an `AbstractInverseRetractionMethod` and `M` is a `Sphere` are introduced.
* `injectivity_radius(M, m)` where `M` is a `Sphere` and `m` is a `LogarithmicInverseRetraction` or a `ProjectionInverseRetraction`.
* `bijectivity_radius(M, p, m)` and `bijectivity_radius(M, m)` are defined for `Sphere`s.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also add tests for this?

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 7.97%. Comparing base (2821510) to head (048e253).

Files with missing lines Patch % Lines
src/manifolds/Sphere.jl 0.00% 23 Missing ⚠️
ext/ManifoldsTestExt/ManifoldsTestExt.jl 0.00% 6 Missing ⚠️
src/Manifolds.jl 0.00% 4 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (2821510) and HEAD (048e253). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (2821510) HEAD (048e253)
4 2
Additional details and impacted files
@@             Coverage Diff             @@
##            master    #915       +/-   ##
===========================================
- Coverage   100.00%   7.97%   -92.03%     
===========================================
  Files           98      98               
  Lines         9830    9712      -118     
===========================================
- Hits          9830     775     -9055     
- Misses           0    8937     +8937     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sblelong

sblelong commented Jul 6, 2026

Copy link
Copy Markdown
Author

Thank you for this detailed feedback, I'll address all changes!

@kellertuer

Copy link
Copy Markdown
Member

As a concrete example – I could convince the tests a bit to run and this

https://github.com/JuliaManifolds/Manifolds.jl/actions/runs/28779489714/job/85391973067?pr=915#step:6:544

should never happen. You did not touch Stiefel at all, but something you changed made that fail now.

You did change expected numbers in the tests (and whether that is breaking is something we should discuss/conclude in the end) – but nothing else should change – otherwise we have a breaking change.

You can also test this locally. Do you know about development mode and ] test Manifolds to run the tests locally?
If you feel a bit lost, we can check to have a short meeting, but not this week, since officially I am on vacation.

@sblelong

sblelong commented Jul 7, 2026

Copy link
Copy Markdown
Author

I was indeed negligent with running tests locally: I ran the The Spheres testset only. I do know about the ] test Manifolds command, but wrongly decided not to test thoroughly this time. My mistake.

The error encountered in the CI brings an interesting question though: as soon as an inverse retraction is defined for a manifold, should we expect the injectivity radius for this inverse retraction to be implemented?

@kellertuer

Copy link
Copy Markdown
Member

No worries, errors are also always a good occasion to learn.

The Spheres testset only.

Yes, we made the possible on purpose, since the overall tests run for a while, one can focus on a single one of on does work only on these. You however also introduce a new signature globally!

as soon as an inverse retraction is defined for a manifold, should we expect the injectivity radius for this inverse retraction to be implemented?

That is an interesting point that affects the test suite (so not whether something is breaking) – For consistency, I think it would be good if it were implemented for all manifolds we have that now run the new tests, and I think a reasonable start is to set it to the same value as the retraction case – but explicitly to that number and add a doctoring to that implementation as well.
That is a path that takes a bit more time to implement, but does not require so much “thinking” in the sense that you have to prove something or look something up (mainly copy a function and its docs and adapt it)
Would you be available to do that?

The alternative is to make the new tests inactive by default and introduce a boolean keyword – though that might also lead to us “forgetting” to add these functions. But it is less work for sure.

@sblelong

sblelong commented Jul 7, 2026

Copy link
Copy Markdown
Author

Yes, we made the possible on purpose, since the overall tests run for a while, one can focus on a single one of on does work only on these. You however also introduce a new signature globally!

And that would have been a good reason for me to be more patient and run the tests entirely before opening the PR! One lesson learned. :)

That is an interesting point that affects the test suite (so not whether something is breaking) – For consistency, I think it would be good if it were implemented for all manifolds we have that now run the new tests, and I think a reasonable start is to set it to the same value as the retraction case – but explicitly to that number and add a doctoring to that implementation as well. That is a path that takes a bit more time to implement, but does not require so much “thinking” in the sense that you have to prove something or look something up (mainly copy a function and its docs and adapt it) Would you be available to do that?

That sounds feasible. I am available and will work on that in the upcoming days, while addressing the other issues raised in this PR.

@kellertuer

Copy link
Copy Markdown
Member

That sounds feasible. I am available and will work on that in the upcoming days, while addressing the other issues raised in this PR.

Nice, thanks! And no worries, a PR is nearly never a sprint, I always prefer thoroughness and completeness (and less bugs) over speed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants