From 6a05d5c7c9d210369562cbe4c1483ecef758dcc9 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Fri, 25 Aug 2023 11:54:10 +0200 Subject: [PATCH 01/13] A first test that you now can run the tests as a script from command line but equivalently from the old ] test Manifolds --- test/runtests.jl | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) mode change 100644 => 100755 test/runtests.jl diff --git a/test/runtests.jl b/test/runtests.jl old mode 100644 new mode 100755 index 372bf55221..a5be04c699 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,21 @@ +#!/usr/bin/env julia +# +# To run the Manifolds.jl Tests as a script with Command line options, +# You have to have TestEvn in your environment. +# +# Command Line Options (stil WIP) +# + +if (abspath(PROGRAM_FILE) == @__FILE__) # Run as script + using TestEnv + println("Script.") + TestEnv.activate("Manifolds") + # Setup test variants + #...then continue with the test run in normal mode below +end + +println("normal.") + include("utils.jl") @info "Manifolds.jl Test settings:\n\n" * From cfcf315fee7bc839913e4ce8c0b2c176ed440b93 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Tue, 12 Sep 2023 18:18:35 +0200 Subject: [PATCH 02/13] Sketch a new test suite. --- Project.toml | 1 + test/run_legacy_tests.jl | 219 +++++++++++++++++++++++++++++ test/runtests.jl | 279 +++++++++---------------------------- test/runtests_cmd_utils.jl | 67 +++++++++ 4 files changed, 349 insertions(+), 217 deletions(-) create mode 100644 test/run_legacy_tests.jl create mode 100644 test/runtests_cmd_utils.jl diff --git a/Project.toml b/Project.toml index 0612503698..9f79d0b220 100644 --- a/Project.toml +++ b/Project.toml @@ -79,6 +79,7 @@ PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" QuartzImageIO = "dca85d43-d64c-5e67-8c65-017450d5d020" Quaternions = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +TestEnv = "1e6cf692-eddd-4d53-88a5-2d735e33781b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" diff --git a/test/run_legacy_tests.jl b/test/run_legacy_tests.jl new file mode 100644 index 0000000000..c3c82b1696 --- /dev/null +++ b/test/run_legacy_tests.jl @@ -0,0 +1,219 @@ +# +# This is the old test run, that should slowly be reduced in size +# + +@info "Manifolds.jl Test settings:\n\n" * + "Testing Float32: $(TEST_FLOAT32)\n" * + "Testing Double64: $(TEST_DOUBLE64)\n" * + "Testing Static: $(TEST_STATIC_SIZED)\n\n" * + "Test group: $(TEST_GROUP)\n\n" * + "These settings are stored in environment variables, see in test/utils.jl" + +@testset "Manifolds.jl" begin + if TEST_GROUP ∈ ["all", "test_manifolds"] + include_test("differentiation.jl") + include_test("ambiguities.jl") + include_test("test_deprecated.jl") + @testset "utils test" begin + Random.seed!(42) + @testset "usinc_from_cos" begin + @test Manifolds.usinc_from_cos(-1) == 0 + @test Manifolds.usinc_from_cos(-1.0) == 0.0 + end + @testset "log_safe!" begin + n = 8 + Q = qr(randn(n, n)).Q + A1 = Matrix(Hermitian(Q * Diagonal(rand(n)) * Q')) + @test exp(Manifolds.log_safe!(similar(A1), A1)) ≈ A1 atol = 1e-6 + A1_fail = Matrix(Hermitian(Q * Diagonal([-1; rand(n - 1)]) * Q')) + @test_throws DomainError Manifolds.log_safe!(similar(A1_fail), A1_fail) + + T = triu!(randn(n, n)) + T[diagind(T)] .= rand.() + @test exp(Manifolds.log_safe!(similar(T), T)) ≈ T atol = 1e-6 + T_fail = copy(T) + T_fail[1] = -1 + @test_throws DomainError Manifolds.log_safe!(similar(T_fail), T_fail) + + A2 = Q * T * Q' + @test exp(Manifolds.log_safe!(similar(A2), A2)) ≈ A2 atol = 1e-6 + A2_fail = Q * T_fail * Q' + @test_throws DomainError Manifolds.log_safe!(similar(A2_fail), A2_fail) + + A3 = exp(SizedMatrix{n,n}(randn(n, n))) + @test A3 isa SizedMatrix + @test exp(Manifolds.log_safe!(similar(A3), A3)) ≈ A3 atol = 1e-6 + @test exp(Manifolds.log_safe(A3)) ≈ A3 atol = 1e-6 + + A3_fail = Float64[1 2; 3 1] + @test_throws DomainError Manifolds.log_safe!(similar(A3_fail), A3_fail) + + A4 = randn(ComplexF64, n, n) + @test exp(Manifolds.log_safe!(similar(A4), A4)) ≈ A4 atol = 1e-6 + end + @testset "isnormal" begin + @test !Manifolds.isnormal([1.0 2.0; 3.0 4.0]) + @test !Manifolds.isnormal(complex.(reshape(1:4, 2, 2), reshape(5:8, 2, 2))) + + # diagonal + @test Manifolds.isnormal(diagm(randn(5))) + @test Manifolds.isnormal(diagm(randn(ComplexF64, 5))) + @test Manifolds.isnormal(Diagonal(randn(5))) + @test Manifolds.isnormal(Diagonal(randn(ComplexF64, 5))) + + # symmetric/hermitian + @test Manifolds.isnormal(Symmetric(randn(3, 3))) + @test Manifolds.isnormal(Hermitian(randn(3, 3))) + @test Manifolds.isnormal(Hermitian(randn(ComplexF64, 3, 3))) + x = Matrix(Symmetric(randn(3, 3))) + x[3, 1] += eps() + @test !Manifolds.isnormal(x) + @test Manifolds.isnormal(x; atol=sqrt(eps())) + + # skew-symmetric/skew-hermitian + skew(x) = x - x' + @test Manifolds.isnormal(skew(randn(3, 3))) + @test Manifolds.isnormal(skew(randn(ComplexF64, 3, 3))) + + # orthogonal/unitary + @test Manifolds.isnormal(Matrix(qr(randn(3, 3)).Q); atol=sqrt(eps())) + @test Manifolds.isnormal( + Matrix(qr(randn(ComplexF64, 3, 3)).Q); + atol=sqrt(eps()), + ) + end + @testset "realify/unrealify!" begin + # round trip real + x = randn(3, 3) + @test Manifolds.realify(x, ℝ) === x + @test Manifolds.unrealify!(similar(x), x, ℝ) == x + + # round trip complex + x2 = randn(ComplexF64, 3, 3) + x2r = Manifolds.realify(x2, ℂ) + @test eltype(x2r) <: Real + @test size(x2r) == (6, 6) + x2c = Manifolds.unrealify!(similar(x2), x2r, ℂ) + @test x2c ≈ x2 + + # matrix multiplication is preserved + x3 = randn(ComplexF64, 3, 3) + x3r = Manifolds.realify(x3, ℂ) + @test x2 * x3 ≈ Manifolds.unrealify!(similar(x2), x2r * x3r, ℂ) + end + @testset "allocation" begin + @test allocate([1 2; 3 4], Float64, Size(3, 3)) isa Matrix{Float64} + @test allocate(SA[1 2; 3 4], Float64, Size(3, 3)) isa MMatrix{3,3,Float64} + @test allocate(SA[1 2; 3 4], Size(3, 3)) isa MMatrix{3,3,Int} + @test Manifolds.quat_promote(Float64) === Quaternions.QuaternionF64 + @test Manifolds.quat_promote(Float32) === Quaternions.QuaternionF32 + @test Manifolds.quat_promote(QuaternionF64) === Quaternions.QuaternionF64 + @test Manifolds.quat_promote(QuaternionF32) === Quaternions.QuaternionF32 + end + @testset "eigen_safe" begin + @test Manifolds.eigen_safe(SA[1.0 0.0; 0.0 1.0]) isa + Eigen{Float64,Float64,<:SizedMatrix{2,2},<:SizedVector{2}} + end + @testset "max_eps" begin + x64 = randn(Float64, 2) + x32 = randn(Float32, 2) + z32 = randn(ComplexF32, 2) + xi = rand(0:1, 2) + @test Manifolds.max_eps(x64, x64) == eps() + @test Manifolds.max_eps(x64, x32) == eps(Float32) + @test Manifolds.max_eps(x32, x64) == eps(Float32) + @test Manifolds.max_eps(xi, xi) == 0 + @test Manifolds.max_eps(xi, x64) == eps() + @test Manifolds.max_eps(xi, x32) == eps(Float32) + @test Manifolds.max_eps(xi, z32) == eps(Float32) + @test Manifolds.max_eps(xi, x64, x32, z32) == eps(Float32) + end + end + + @test Manifolds.is_metric_function(flat) + @test Manifolds.is_metric_function(sharp) + + include_test("groups/group_utils.jl") + include_test("notation.jl") + # starting with tests of simple manifolds + include_test("manifolds/centered_matrices.jl") + include_test("manifolds/circle.jl") + include_test("manifolds/cholesky_space.jl") + include_test("manifolds/elliptope.jl") + include_test("manifolds/euclidean.jl") + include_test("manifolds/fixed_rank.jl") + include_test("manifolds/flag.jl") + include_test("manifolds/generalized_grassmann.jl") + include_test("manifolds/generalized_stiefel.jl") + include_test("manifolds/grassmann.jl") + include_test("manifolds/hyperbolic.jl") + include_test("manifolds/lorentz.jl") + include_test("manifolds/multinomial_doubly_stochastic.jl") + include_test("manifolds/multinomial_symmetric.jl") + include_test("manifolds/positive_numbers.jl") + include_test("manifolds/probability_simplex.jl") + include_test("manifolds/projective_space.jl") + include_test("manifolds/rotations.jl") + include_test("manifolds/shape_space.jl") + include_test("manifolds/skewhermitian.jl") + include_test("manifolds/spectrahedron.jl") + include_test("manifolds/sphere.jl") + include_test("manifolds/sphere_symmetric_matrices.jl") + include_test("manifolds/stiefel.jl") + include_test("manifolds/symmetric.jl") + include_test("manifolds/symmetric_positive_definite.jl") + include_test("manifolds/spd_fixed_determinant.jl") + include_test("manifolds/symmetric_positive_semidefinite_fixed_rank.jl") + include_test("manifolds/symplectic.jl") + include_test("manifolds/symplecticstiefel.jl") + include_test("manifolds/tucker.jl") + include_test("manifolds/unitary_matrices.jl") + + include_test("manifolds/essential_manifold.jl") + include_test("manifolds/multinomial_matrices.jl") + include_test("manifolds/oblique.jl") + include_test("manifolds/torus.jl") + + #meta manifolds + include_test("manifolds/product_manifold.jl") + include_test("manifolds/power_manifold.jl") + include_test("manifolds/quotient_manifold.jl") + include_test("manifolds/vector_bundle.jl") + include_test("manifolds/graph.jl") + + include_test("metric.jl") + include_test("statistics.jl") + end + + if TEST_GROUP ∈ ["all", "test_integration"] + include_test("approx_inverse_retraction.jl") + + # manifolds requiring ODE solvers + include_test("manifolds/embedded_torus.jl") + end + + if TEST_GROUP ∈ ["test_lie_groups", "all"] + # Lie groups and actions + include_test("groups/groups_general.jl") + include_test("groups/validation_group.jl") + include_test("groups/circle_group.jl") + include_test("groups/translation_group.jl") + include_test("groups/general_linear.jl") + include_test("groups/general_unitary_groups.jl") + include_test("groups/special_linear.jl") + include_test("groups/special_orthogonal.jl") + include_test("groups/heisenberg.jl") + include_test("groups/product_group.jl") + include_test("groups/semidirect_product_group.jl") + include_test("groups/power_group.jl") + include_test("groups/special_euclidean.jl") + include_test("groups/group_operation_action.jl") + include_test("groups/rotation_action.jl") + include_test("groups/translation_action.jl") + include_test("groups/connections.jl") + include_test("groups/metric.jl") + end + if TEST_GROUP ∈ ["all", "test_integration"] && !Sys.isapple() + include_test("recipes.jl") + end +end diff --git a/test/runtests.jl b/test/runtests.jl index a5be04c699..e7a2aa9c3d 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,231 +5,76 @@ # # Command Line Options (stil WIP) # +include("runtests_cmd_utils.jl") if (abspath(PROGRAM_FILE) == @__FILE__) # Run as script + if "--help" ∈ lowercase.(ARGS) + println( + """ + # Manifolds.jl Test Suite Command Line Arguments + + * `--exclude-manifold` - Specify manifold(s) to exclude (either from all or from the `--manifold` argument) + * `--exclude-function` - Specify manifold(s) to exclude (either from all or from the `--manifold` argument) + * `--help` - print this help + * `--manifold` - Specify manifold(s) to test (only these are tested) + * `--function` - Specify manifold(s) to test (only these are tested) + * `--element-type` - (planned feature) Specify element type(s) to use in default arrays (default: `Float64`) + + ## Examples + + * `./runtests.jl` runs the default (full suite) + * `./runtests.jl --manifold Sphere` only run the tests on the sphere(s) + * `./runtests.jl --function exp` only run the `exp`onential map tests + * `./runtests.jl --exclude-function exp` run all tests _except `exp`onential map tests + """, + ) + exit() + end using TestEnv - println("Script.") TestEnv.activate("Manifolds") - # Setup test variants - #...then continue with the test run in normal mode below -end - -println("normal.") - -include("utils.jl") - -@info "Manifolds.jl Test settings:\n\n" * - "Testing Float32: $(TEST_FLOAT32)\n" * - "Testing Double64: $(TEST_DOUBLE64)\n" * - "Testing Static: $(TEST_STATIC_SIZED)\n\n" * - "Test group: $(TEST_GROUP)\n\n" * - "These settings are stored in environment variables, see in test/utils.jl" - -@testset "Manifolds.jl" begin - if TEST_GROUP ∈ ["all", "test_manifolds"] - include_test("differentiation.jl") - include_test("ambiguities.jl") - include_test("test_deprecated.jl") - @testset "utils test" begin - Random.seed!(42) - @testset "usinc_from_cos" begin - @test Manifolds.usinc_from_cos(-1) == 0 - @test Manifolds.usinc_from_cos(-1.0) == 0.0 - end - @testset "log_safe!" begin - n = 8 - Q = qr(randn(n, n)).Q - A1 = Matrix(Hermitian(Q * Diagonal(rand(n)) * Q')) - @test exp(Manifolds.log_safe!(similar(A1), A1)) ≈ A1 atol = 1e-6 - A1_fail = Matrix(Hermitian(Q * Diagonal([-1; rand(n - 1)]) * Q')) - @test_throws DomainError Manifolds.log_safe!(similar(A1_fail), A1_fail) - - T = triu!(randn(n, n)) - T[diagind(T)] .= rand.() - @test exp(Manifolds.log_safe!(similar(T), T)) ≈ T atol = 1e-6 - T_fail = copy(T) - T_fail[1] = -1 - @test_throws DomainError Manifolds.log_safe!(similar(T_fail), T_fail) - - A2 = Q * T * Q' - @test exp(Manifolds.log_safe!(similar(A2), A2)) ≈ A2 atol = 1e-6 - A2_fail = Q * T_fail * Q' - @test_throws DomainError Manifolds.log_safe!(similar(A2_fail), A2_fail) - - A3 = exp(SizedMatrix{n,n}(randn(n, n))) - @test A3 isa SizedMatrix - @test exp(Manifolds.log_safe!(similar(A3), A3)) ≈ A3 atol = 1e-6 - @test exp(Manifolds.log_safe(A3)) ≈ A3 atol = 1e-6 - - A3_fail = Float64[1 2; 3 1] - @test_throws DomainError Manifolds.log_safe!(similar(A3_fail), A3_fail) - - A4 = randn(ComplexF64, n, n) - @test exp(Manifolds.log_safe!(similar(A4), A4)) ≈ A4 atol = 1e-6 - end - @testset "isnormal" begin - @test !Manifolds.isnormal([1.0 2.0; 3.0 4.0]) - @test !Manifolds.isnormal(complex.(reshape(1:4, 2, 2), reshape(5:8, 2, 2))) - - # diagonal - @test Manifolds.isnormal(diagm(randn(5))) - @test Manifolds.isnormal(diagm(randn(ComplexF64, 5))) - @test Manifolds.isnormal(Diagonal(randn(5))) - @test Manifolds.isnormal(Diagonal(randn(ComplexF64, 5))) - - # symmetric/hermitian - @test Manifolds.isnormal(Symmetric(randn(3, 3))) - @test Manifolds.isnormal(Hermitian(randn(3, 3))) - @test Manifolds.isnormal(Hermitian(randn(ComplexF64, 3, 3))) - x = Matrix(Symmetric(randn(3, 3))) - x[3, 1] += eps() - @test !Manifolds.isnormal(x) - @test Manifolds.isnormal(x; atol=sqrt(eps())) - - # skew-symmetric/skew-hermitian - skew(x) = x - x' - @test Manifolds.isnormal(skew(randn(3, 3))) - @test Manifolds.isnormal(skew(randn(ComplexF64, 3, 3))) - - # orthogonal/unitary - @test Manifolds.isnormal(Matrix(qr(randn(3, 3)).Q); atol=sqrt(eps())) - @test Manifolds.isnormal( - Matrix(qr(randn(ComplexF64, 3, 3)).Q); - atol=sqrt(eps()), - ) + using Manifolds + M = Sphere(2) + i = 1 + only_manifolds = Type[] + exclude_manifolds = Type[] + only_functions = Function[] + exclude_functions = Function[] + while i <= length(ARGS) + if startswith(ARGS[i], "--") + cmd = ARGS[i][3:end] + if lowercase(cmd) == "manifold" + (j, candidates) = collect_args(i + 1, ARGS) + global i = j #advance to next field + global only_manifolds = process_manifolds(candidates) end - @testset "realify/unrealify!" begin - # round trip real - x = randn(3, 3) - @test Manifolds.realify(x, ℝ) === x - @test Manifolds.unrealify!(similar(x), x, ℝ) == x - - # round trip complex - x2 = randn(ComplexF64, 3, 3) - x2r = Manifolds.realify(x2, ℂ) - @test eltype(x2r) <: Real - @test size(x2r) == (6, 6) - x2c = Manifolds.unrealify!(similar(x2), x2r, ℂ) - @test x2c ≈ x2 - - # matrix multiplication is preserved - x3 = randn(ComplexF64, 3, 3) - x3r = Manifolds.realify(x3, ℂ) - @test x2 * x3 ≈ Manifolds.unrealify!(similar(x2), x2r * x3r, ℂ) + if lowercase(cmd) == "exclude-manifold" + (j, candidates) = collect_args(i + 1, ARGS) + global i = j #advance to next field + global exclude_manifolds = process_manifolds(candidates) end - @testset "allocation" begin - @test allocate([1 2; 3 4], Float64, Size(3, 3)) isa Matrix{Float64} - @test allocate(SA[1 2; 3 4], Float64, Size(3, 3)) isa MMatrix{3,3,Float64} - @test allocate(SA[1 2; 3 4], Size(3, 3)) isa MMatrix{3,3,Int} - @test Manifolds.quat_promote(Float64) === Quaternions.QuaternionF64 - @test Manifolds.quat_promote(Float32) === Quaternions.QuaternionF32 - @test Manifolds.quat_promote(QuaternionF64) === Quaternions.QuaternionF64 - @test Manifolds.quat_promote(QuaternionF32) === Quaternions.QuaternionF32 + if lowercase(cmd) == "function" + (j, candidates) = collect_args(i + 1, ARGS) + global i = j #advance to next field + global only_functions = process_functions(candidates) end - @testset "eigen_safe" begin - @test Manifolds.eigen_safe(SA[1.0 0.0; 0.0 1.0]) isa - Eigen{Float64,Float64,<:SizedMatrix{2,2},<:SizedVector{2}} - end - @testset "max_eps" begin - x64 = randn(Float64, 2) - x32 = randn(Float32, 2) - z32 = randn(ComplexF32, 2) - xi = rand(0:1, 2) - @test Manifolds.max_eps(x64, x64) == eps() - @test Manifolds.max_eps(x64, x32) == eps(Float32) - @test Manifolds.max_eps(x32, x64) == eps(Float32) - @test Manifolds.max_eps(xi, xi) == 0 - @test Manifolds.max_eps(xi, x64) == eps() - @test Manifolds.max_eps(xi, x32) == eps(Float32) - @test Manifolds.max_eps(xi, z32) == eps(Float32) - @test Manifolds.max_eps(xi, x64, x32, z32) == eps(Float32) + if lowercase(cmd) == "exclude-function" + (j, candidates) = collect_args(i + 1, ARGS) + global i = j #advance to next field + global exclude_functions = process_manifolds(candidates) end + else + global i += 1 end - - @test Manifolds.is_metric_function(flat) - @test Manifolds.is_metric_function(sharp) - - include_test("groups/group_utils.jl") - include_test("notation.jl") - # starting with tests of simple manifolds - include_test("manifolds/centered_matrices.jl") - include_test("manifolds/circle.jl") - include_test("manifolds/cholesky_space.jl") - include_test("manifolds/elliptope.jl") - include_test("manifolds/euclidean.jl") - include_test("manifolds/fixed_rank.jl") - include_test("manifolds/flag.jl") - include_test("manifolds/generalized_grassmann.jl") - include_test("manifolds/generalized_stiefel.jl") - include_test("manifolds/grassmann.jl") - include_test("manifolds/hyperbolic.jl") - include_test("manifolds/lorentz.jl") - include_test("manifolds/multinomial_doubly_stochastic.jl") - include_test("manifolds/multinomial_symmetric.jl") - include_test("manifolds/positive_numbers.jl") - include_test("manifolds/probability_simplex.jl") - include_test("manifolds/projective_space.jl") - include_test("manifolds/rotations.jl") - include_test("manifolds/shape_space.jl") - include_test("manifolds/skewhermitian.jl") - include_test("manifolds/spectrahedron.jl") - include_test("manifolds/sphere.jl") - include_test("manifolds/sphere_symmetric_matrices.jl") - include_test("manifolds/stiefel.jl") - include_test("manifolds/symmetric.jl") - include_test("manifolds/symmetric_positive_definite.jl") - include_test("manifolds/spd_fixed_determinant.jl") - include_test("manifolds/symmetric_positive_semidefinite_fixed_rank.jl") - include_test("manifolds/symplectic.jl") - include_test("manifolds/symplecticstiefel.jl") - include_test("manifolds/tucker.jl") - include_test("manifolds/unitary_matrices.jl") - - include_test("manifolds/essential_manifold.jl") - include_test("manifolds/multinomial_matrices.jl") - include_test("manifolds/oblique.jl") - include_test("manifolds/torus.jl") - - #meta manifolds - include_test("manifolds/product_manifold.jl") - include_test("manifolds/power_manifold.jl") - include_test("manifolds/quotient_manifold.jl") - include_test("manifolds/vector_bundle.jl") - include_test("manifolds/graph.jl") - - include_test("metric.jl") - include_test("statistics.jl") - end - - if TEST_GROUP ∈ ["all", "test_integration"] - include_test("approx_inverse_retraction.jl") - - # manifolds requiring ODE solvers - include_test("manifolds/embedded_torus.jl") - end - - if TEST_GROUP ∈ ["test_lie_groups", "all"] - # Lie groups and actions - include_test("groups/groups_general.jl") - include_test("groups/validation_group.jl") - include_test("groups/circle_group.jl") - include_test("groups/translation_group.jl") - include_test("groups/general_linear.jl") - include_test("groups/general_unitary_groups.jl") - include_test("groups/special_linear.jl") - include_test("groups/special_orthogonal.jl") - include_test("groups/heisenberg.jl") - include_test("groups/product_group.jl") - include_test("groups/semidirect_product_group.jl") - include_test("groups/power_group.jl") - include_test("groups/special_euclidean.jl") - include_test("groups/group_operation_action.jl") - include_test("groups/rotation_action.jl") - include_test("groups/translation_action.jl") - include_test("groups/connections.jl") - include_test("groups/metric.jl") - end - if TEST_GROUP ∈ ["all", "test_integration"] && !Sys.isapple() - include_test("recipes.jl") end + # Set these in ENV + ENV["MANIFOLDS_TESTSUITE_ONLY_MANIFOLDS"] = only_manifolds + ENV["MANIFOLDS_TESTSUITE_EXCLUDE_MANIFOLDS"] = only_manifolds + ENV["MANIFOLDS_TESTSUITE_ONLY_FUNCTIONS"] = only_functions + ENV["MANIFOLDS_TESTSUITE_EXCLUDE_FUNCTIONS"] = exclude_functions + print(ENV) + # Setup test variants + #...then continue with the test run in normal mode below end +include("utils.jl") + +include("run_legacy_tests.jl") diff --git a/test/runtests_cmd_utils.jl b/test/runtests_cmd_utils.jl new file mode 100644 index 0000000000..e50931fb1f --- /dev/null +++ b/test/runtests_cmd_utils.jl @@ -0,0 +1,67 @@ +""" + process_manifolds(args, warn=true) + +Given a vector of strings of manifold types, check whether they exist and add them +to an array of types. +""" +function process_manifolds(args::Vector{String}, warn::Bool=true) + manifolds = Type[] + for s in args + m = Symbol(s) + if hasproperty(Manifolds, m) + M = getproperty(Manifolds, m) + if M <: AbstractManifold + push!(manifolds, M) + else + warn && (@warn "`$s` seems not to be a manifold.") + end + else + warn && (@warn "`$s` seems not to be defined in `Manifolds` namespace.") + end + end + return manifolds +end +""" + process_functionds(args, warn=true) + +Given a vector of strings of functions types, check whether they exist and add them +to an array of Functions. +""" +function process_functions(args::Vector{String}, warn::Bool=true) + functions = Function[] + for s in args + m = Symbol(s) + if hasproperty(Manifolds, m) + f = getproperty(Manifolds, m) + if f isa Function + push!(functions, f) + else + warn && (@warn "`$s` seems not to be a function.") + end + else + warn && (@warn "`$s` seems not to be defined in `Main` namespace.") + end + end + return functions +end + +""" + collect_args(start, args) + +Given a vector of arguments, and a start, this function collects all +arguments at start, start+1,... until an argument starting with `--` appears. +It returns a tupe (j, candidates) +of the collected arguments as `candidates` and `j` indicating the first argment +not collected, i.e. the one starting again with `--`. +""" +function collect_args(start, args) + candidates = String[] + # collect manifolds + i = start + while i <= length(args) + startswith(args[i], "--") && break + push!(candidates, args[i]) + i = i + 1 + end + return (i, candidates) +end From 50e29efab5fd484f2708511a8da3c3ec5b88b99d Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Tue, 12 Sep 2023 19:25:34 +0200 Subject: [PATCH 03/13] extract the old design of a features functions --- src/Manifolds.jl | 3 +- src/utils/features.jl | 383 +++++++++++++++++++++++++++++ src/{utils.jl => utils/helpers.jl} | 0 3 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 src/utils/features.jl rename src/{utils.jl => utils/helpers.jl} (100%) diff --git a/src/Manifolds.jl b/src/Manifolds.jl index 259c4a7b17..5fe2468dc6 100644 --- a/src/Manifolds.jl +++ b/src/Manifolds.jl @@ -329,7 +329,7 @@ using Statistics using StatsBase using StatsBase: AbstractWeights -include("utils.jl") +include("utils/helpers.jl") include("product_representations.jl") @@ -470,6 +470,7 @@ include("groups/special_euclidean.jl") # final utilities include("trait_recursion_breaking.jl") +include("utils/features.jl") @doc raw""" Base.in(p, M::AbstractManifold; kwargs...) diff --git a/src/utils/features.jl b/src/utils/features.jl new file mode 100644 index 0000000000..7d774b58b0 --- /dev/null +++ b/src/utils/features.jl @@ -0,0 +1,383 @@ +# +# Towards more automated testing – let's find Methods available on a manifold automatically +# but due to decorators, we have to test_try_calls +# +@doc raw""" + find_manifold_functions(M; kwargs...) + +Find functions that are available on a manifold by trying to call them. + +Returns a vector of functions. + +Note that `retract`, `inverse_retract` and `vector_transport` are only tested if a default +is available (calling e.g. `retract(M, p, X)`) for exactly that default, i.e. calling `retract(M, p, X)`. +Their more precise versions checking for existing variants are +[`find_retractions`](@ref), [`find_inverse_retractions`](@ref), [`find_vector_transports`](@ref) + +Note that this only includes the high-level / exported functions. +The test also just tests the allocating variants, since by default these are assumed to +allocate and call the in-place variants + +# Keyword Arguments + +The methods might depend on which types of points, tangent vectors or numbers are chosen: + +* `p` – (`rand(M)`) a point +* `X` - (`rand(M; vector_at=p)`)` a tangent vector +* `t` – (`1.0`) a value +""" +function find_manifold_functions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) + # This is a bit of an exhaustive list of try-catch checks, since we just try whether something does not fail + features = Function[] + # + # a) signature f(M) + for f in [injectivity_radius, manifold_dimension, rand, representation_size] + try + f(M) + push!(features, f) + catch + end + end + # b) signature f(M, p) + for f in [copy, is_point, embed, zero_vector] + try + f(M, p) + push!(features, f) + catch + end + end + # c) signature f(M, p, X) + for f in [exp, geodesic, is_vector, norm, retract] + try + f(M, p, X) + push!(features, f) + catch + end + end + # d) signatures f(M, p, q) + for f in [distance, inverse_retract, log, shortest_geodesic] + try + f(M, p, p) + push!(features, f) + catch + end + end + # e) signature f(M, p, X, q) + for f in [parallel_transport_to, vector_transport_to] + try + f(M, p, X, p) + push!(features, f) + catch + end + end + # f) signature f(M, p, X, Y) + for f in [parallel_transport_direction, vector_transport_direction] + try + f(M, p, X, X) + push!(features, f) + catch + end + end + # f) signature f(M, p, X, Y, Z) + for f in [riemann_tensor] + try + f(M, p, X, X, X) + push!(features, f) + catch + end + end + return features +end +@doc raw""" + find_manifold_retractions(M; kwargs...) + +Find retractions that are available on a manifold by trying to call them. + +Returns a vector of retractions. +The set of retractions is automatically determined from all available subtypes of +`AbstractRetractionMethod`, excluding a few “meta retractions” like the `ProductRetraction`, +or the `EmbeddedRetraction`, since these are automatically available if the manifold has +certain properties without them beding implemented necessarily. + +# Keyword Arguments + +The methods might depend on which types of points, tangent vectors or numbers are chosen: + +* `p` – (`rand(M)`) a point +* `X` - (`rand(M; vector_at=p)`)` a tangent vector +* `t` – (`1.0`) a value +""" +function find_manifold_retractions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) + checks = AbstractRetractionMethod[] + # The following can only be checked on certain manifolds and/or need parameters + auto_excl = [ + EmbeddedRetraction, + RetractionWithKeywords, + ODEExponentialRetraction, + PadeRetraction, + ProductRetraction, # generic on Products + SasakiRetraction, # generic on tangent bundle + ] + for T in subtypes(AbstractRetractionMethod) + if !isabstracttype(T) && T ∉ auto_excl + push!(checks, T()) + end + end + push!(checks, PadeRetraction(2)) # Since order one might just fall back to Caley + try #if we have an emebdding try embedded retraction with their default one + push!(checks, EmbeddedRetraction(default_retraction_method(get_embedding(M)))) + catch + end + if default_retraction_method(M) != ExponentialRetraction() + push!(checks, ODEExponentialRetraction(default_retraction_method(M))) + end + if M isa ProductManifold + push!( + checks, + ProductRetraction([default_retraction_method(N) for N in M.manifolds]...), + ) + end + if M isa TangentBundle + push!(checks, SasakiRetraction(1)) + end + # + # Ok – Let's check them + retr_features = AbstractRetractionMethod[] + for retr in checks + try + retract(M, p, X, retr) + push!(retr_features, retr) + catch + end + end + return retr_features +end + +function find_manifold_inverse_retractions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) + checks = AbstractInverseRetractionMethod[] + # The following can only be checked on certain manifolds and/or need parameters + auto_excl = [ + EmbeddedInverseRetraction, + PadeInverseRetraction, + InverseProductRetraction, + InverseRetractionWithKeywords, + ] + for T in subtypes(AbstractInverseRetractionMethod) #Check all existing ones besides the abstract ones + if !isabstracttype(T) && T ∉ auto_excl + push!(checks, T()) + end + end + push!(checks, PadeInverseRetraction(2)) # Since order one might just fall back to Caley + try #if we have an emebdding try embedded retraction with their default one + push!( + checks, + EmbeddedInverseRetraction(default_inverse_retraction_method(get_embedding(M))), + ) + catch + end + if M isa ProductManifold + push!( + checks, + InverseProductRetraction( + [default_inverse_retraction_method(N) for N in M.manifolds]..., + ), + ) + end + # + # Ok – Let's check them + inv_retr_features = AbstractInverseRetractionMethod[] + for inv_retr in checks + try + inverse_retract(M, p, p, inv_retr) + push!(inv_retr_features, inv_retr) + catch + end + end + return inv_retr_features +end + +function find_manifold_vector_transports(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) + checks = AbstractVectorTransportMethod[] + # The following can only be checked on certain manifolds and/or need parameters + auto_excl = [ + ScaledVectorTransport, + VectorTransportWithKeywords, + DifferentiatedRetractionVectorTransport, + VectorTransportTo, #only generic for internal use + ] + for T in [ + subtypes(AbstractVectorTransportMethod)..., + subtypes(AbstractLinearVectorTransportMethod)..., + ] + if !isabstracttype(T) && T ∉ auto_excl + push!(checks, T()) + end + end + try + push!(checks, DifferentiatedRetractionVectorTransport(default_retraction_method(M))) + catch + end + try #if we have an emebdding try embedded retraction with their default one + push!(checks, ScaledVectorTransport(default_vector_transport_method(M))) + catch + end + # + # Ok – Let's check them + vector_transport_features = AbstractVectorTransportMethod[] + for vector_transport in checks + try + vector_transport_to(M, p, X, p, vector_transport) + push!(vector_transport_features, vector_transport) + catch + end + end + return vector_transport_features +end + +""" + find_manifold_properties(M) + +""" +function find_manifold_properties(M::AbstractManifold) + properties = Dict{Symbol,<:Any}() + return properties +end + + +""" + ManifoldFeatures + +Collect a set of features available on a manifold. + +# Fields + +* `M` – a manifold +* `functions` – available functions on a manifold +* `retractions` - available retractions on a manifold +* `inverseretractions` - available inverse retractions +* `vector_transports` - available vector transports. +* `properties` – further properties stored as symbols that are set to certain values + +All these are filled by default calling the corresponding `find_manifold_` functions +""" +struct ManifoldFeatures{F<:Function} + functions::Vector{F} + retractions::Vector{AbstractRetractionMethod} + inverse_retractions::Vector{AbstractInverseRetractionMethod} + vector_transports::Vector{AbstractVectorTransportMethod} + properties::Dict{Symbol,<:Any} +end +function ManifoldFeatures(; + functions::Vector{F}=Functions[], + retractions=Vector{AbstractRetractionMethod}[], + inverse_retractions=Vector{AbstractInverseRetractionMethod}[], + vector_transports=Vector{AbstractVectorTransportMethod}[], + properties=Dict{Symbol,Bools}(), +) where {F<:Function} + return ManifoldFeatures{F}( + functions, + retractions, + inverse_retractions, + vector_transports, + properties, + ) +end +function ManifoldFeatures( + M::AbstractManifold; + p=rand(M), + X=rand(M; vector_at=p), + t=1.0, + functions::Vector{F}=find_manifold_functions(M; p=p, X=X, t=t), + retractions::Vector{R}=find_manifold_retractions(M; p=p, X=X, t=t), + inverse_retractions::Vector{I}=find_manifold_inverse_retractions(M; p=p, X=X, t=t), + vector_transports::Vector{V}=find_manifold_vector_transports(M; p=p, X=X, t=t), + properties=Dict{Symbol,String}(), +) where { + F<:Function, + R<:AbstractRetractionMethod, + I<:AbstractInverseRetractionMethod, + V<:AbstractVectorTransportMethod, +} + return ManifoldFeatures{F}( + functions, + retractions, + inverse_retractions, + vector_transports, + properties, + ) +end +function show(io::IO, mf::ManifoldFeatures) + # Print features to terminal + s = """ + ManifoldFeatures\n\n + + Functions + $(join([" * $(f)" for f in mf.functions],"\n")) + + Retractions + $(join([" * $(typeof(r))" for r in mf.retractions],"\n")) + + Inverse Retractions + $(join([" * $(typeof(ir))" for ir in mf.inverse_retractions],"\n")) + + Vector transports + $(join([" * $(typeof(v))" for v in mf.vector_transports],"\n")) + """ + return print(io, s) +end + +function show(io::IO, ::MIME"test/markdown", mf::ManifoldFeatures) + # Print the features in a nice Markdown table for the docs +end +@doc """ + ManifoldExpectations + +For the features from [`ManifoldFeatures`](@ref) this struct provides expected +values and tolerances + +* `errors` a dictionary `:Symbol-> [...]` for expected errors, e.g. when passing non-points to `:is_point`. +* `values` a dictionary `:Symbol-> [...]` to values, strings or arrays, that we expect for, e.g. `:manifold_dimension` +* `tolerances` a dictionary `:Symbol->Float64` for tolerances in checks of functions. +x""" +struct ManifoldExpectations{E,V,T} + errors::Dict{Symbol,E} + values::Dict{Symbol,V} + tolerances::Dict{Symbol,T} +end +function ManifoldExpectations(; + values::Dict{Symbol,V}=Dict{Symbol,Union{String,Float64,Array{Float64}}}(), + tolerances::Dict{Symbol,T}=Dict(:default => 1e-14), + errors::Dict{Symbol,E}=Dict{Symbol,Any}(), +) where {E,V,T} + return ManifoldExpectations{E,V,T}(errors, values, tolerances) +end + +@doc raw""" + set_expectation!(expectation::ManifoldExpectation, key::Symbol, value) + +Set an expectation of the `key` to `value`. +""" +function set_expectation(expectation::ManifoldExpectations, key::Symbol, value) + expectation.values[key] = value + return expectation +end + +@doc """ + has_feature_expectations(F::ManifoldFeatures, E::ManifoldExpectations, s::symbol) + +For a current set of features and expectations infer whether they provide information +for a symbol `s`. + +For example for `has_feature_expectations(F,E,:exp)` to return `true` +* the function `exp` has to be in the features +* the expectations have to have a tolerance present +""" +function has_feature_expectations(F::ManifoldFeatures, E::ManifoldExpectations, s::Symbol) + # default cases + f = getfield(Main, s) #default: is test a function name or a type? + (f isa Function) && (f in F.functions) && (return true) + (f isa AbstractRetractionMethod) && (f in F.retractions) && (return true) + (f isa AbstractInverseRetractionMethod) && (f in F.retractions) && (return true) + (f isa AbstractVectorTransportMethod) && (f in F.inverse_retractions) && (return true) + return false +end \ No newline at end of file diff --git a/src/utils.jl b/src/utils/helpers.jl similarity index 100% rename from src/utils.jl rename to src/utils/helpers.jl From f0757817cfc8db658a68cfe6a3c1ac3b780da5fb Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Tue, 19 Sep 2023 17:42:44 +0200 Subject: [PATCH 04/13] Improve features run. --- Project.toml | 1 + src/Manifolds.jl | 1 + src/utils/features.jl | 24 +++++++++++------------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Project.toml b/Project.toml index a4355013e3..0e314ba5ed 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Einsum = "b7d42ee7-0b51-5a75-98ca-779d3107e4c0" Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" HybridArrays = "1baab800-613f-4b0a-84e4-9cd3431bfbb9" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Kronecker = "2c470bb0-bcc8-11e8-3dad-c9649493f05e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" ManifoldDiff = "af67fdf4-a580-4b9f-bbec-742ef357defd" diff --git a/src/Manifolds.jl b/src/Manifolds.jl index 5fe2468dc6..654e6c22ac 100644 --- a/src/Manifolds.jl +++ b/src/Manifolds.jl @@ -186,6 +186,7 @@ using Base.Iterators: repeated using Distributions using Einsum: @einsum using HybridArrays +using InteractiveUtils: subtypes using Kronecker using Graphs using LinearAlgebra diff --git a/src/utils/features.jl b/src/utils/features.jl index 7d774b58b0..d608283a25 100644 --- a/src/utils/features.jl +++ b/src/utils/features.jl @@ -111,8 +111,8 @@ function find_manifold_retractions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) checks = AbstractRetractionMethod[] # The following can only be checked on certain manifolds and/or need parameters auto_excl = [ - EmbeddedRetraction, - RetractionWithKeywords, + ManifoldsBase.EmbeddedRetraction, + ManifoldsBase.RetractionWithKeywords, ODEExponentialRetraction, PadeRetraction, ProductRetraction, # generic on Products @@ -157,10 +157,10 @@ function find_manifold_inverse_retractions(M; p=rand(M), X=rand(M; vector_at=p), checks = AbstractInverseRetractionMethod[] # The following can only be checked on certain manifolds and/or need parameters auto_excl = [ - EmbeddedInverseRetraction, + ManifoldsBase.EmbeddedInverseRetraction, PadeInverseRetraction, InverseProductRetraction, - InverseRetractionWithKeywords, + ManifoldsBase.InverseRetractionWithKeywords, ] for T in subtypes(AbstractInverseRetractionMethod) #Check all existing ones besides the abstract ones if !isabstracttype(T) && T ∉ auto_excl @@ -201,9 +201,9 @@ function find_manifold_vector_transports(M; p=rand(M), X=rand(M; vector_at=p), t # The following can only be checked on certain manifolds and/or need parameters auto_excl = [ ScaledVectorTransport, - VectorTransportWithKeywords, + ManifoldsBase.VectorTransportWithKeywords, DifferentiatedRetractionVectorTransport, - VectorTransportTo, #only generic for internal use + ManifoldsBase.VectorTransportTo, #only generic for internal use ] for T in [ subtypes(AbstractVectorTransportMethod)..., @@ -243,7 +243,6 @@ function find_manifold_properties(M::AbstractManifold) return properties end - """ ManifoldFeatures @@ -310,18 +309,17 @@ function show(io::IO, mf::ManifoldFeatures) # Print features to terminal s = """ ManifoldFeatures\n\n - Functions - $(join([" * $(f)" for f in mf.functions],"\n")) + $(join(sort([" * $(f)" for f in mf.functions]),"\n")) Retractions - $(join([" * $(typeof(r))" for r in mf.retractions],"\n")) + $(join(sort([" * $((Base.typename(typeof(r)).name))" for r in mf.retractions]),"\n")) Inverse Retractions - $(join([" * $(typeof(ir))" for ir in mf.inverse_retractions],"\n")) + $(join(sort([" * $(Base.typename(typeof(ir)).name)" for ir in mf.inverse_retractions]),"\n")) Vector transports - $(join([" * $(typeof(v))" for v in mf.vector_transports],"\n")) + $(join(sort([" * $(Base.typename(typeof(v)).name)" for v in mf.vector_transports]),"\n")) """ return print(io, s) end @@ -380,4 +378,4 @@ function has_feature_expectations(F::ManifoldFeatures, E::ManifoldExpectations, (f isa AbstractInverseRetractionMethod) && (f in F.retractions) && (return true) (f isa AbstractVectorTransportMethod) && (f in F.inverse_retractions) && (return true) return false -end \ No newline at end of file +end From 14665277a5a4f7ce4329642f226c7cbc88421dba Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 08:46:36 +0200 Subject: [PATCH 05/13] Fix 2 small typos. --- assets/logo.jl | 2 +- docs/src/assets/logo-dark.svg | 496 ++++++++++++++++++++++++ docs/src/assets/logo.svg | 490 +++++++++++++++++++++++ src/manifolds/StiefelCanonicalMetric.jl | 2 +- 4 files changed, 988 insertions(+), 2 deletions(-) create mode 100644 docs/src/assets/logo-dark.svg create mode 100644 docs/src/assets/logo.svg diff --git a/assets/logo.jl b/assets/logo.jl index 37fd5034e1..9f8ed66301 100644 --- a/assets/logo.jl +++ b/assets/logo.jl @@ -69,7 +69,7 @@ end function plot_geodesic!(ax, M, x, y; n=100, options=Dict()) γ = shortest_geodesic(M, x, y) T = range(0, 1; length=n) - push!(ax, Plot3(options, Coordinates(Tuple.(γ(T))))) + push!(ax, Plot3(options, Coordinates(Tuple.(γ.(T))))) return ax end diff --git a/docs/src/assets/logo-dark.svg b/docs/src/assets/logo-dark.svg new file mode 100644 index 0000000000..7e764e020c --- /dev/null +++ b/docs/src/assets/logo-dark.svg @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg new file mode 100644 index 0000000000..e2d37d6b4d --- /dev/null +++ b/docs/src/assets/logo.svg @@ -0,0 +1,490 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/manifolds/StiefelCanonicalMetric.jl b/src/manifolds/StiefelCanonicalMetric.jl index 9306988fd8..5617e4b822 100644 --- a/src/manifolds/StiefelCanonicalMetric.jl +++ b/src/manifolds/StiefelCanonicalMetric.jl @@ -16,7 +16,7 @@ See [`inverse_retract(::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric} # Fields * `max_iterations` – maximal number of iterations used in the approximation -* `tolerance` – a tolerance used as a stopping criterion +* `tolerance` – a tolerance used as a stopping criterion """ struct ApproximateLogarithmicMap{T} <: ApproximateInverseRetraction From fc28a30eab5a066c0633c05d7e8818961c1d29ca Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 10:25:10 +0200 Subject: [PATCH 06/13] Add Aqua for a better overview. --- Project.toml | 2 +- src/utils/features.jl | 22 ++++++++++++++----- test/ambiguities.jl | 47 ---------------------------------------- test/aqua.jl | 23 ++++++++++++++++++++ test/run_legacy_tests.jl | 4 ++-- test/runtests.jl | 4 ++++ 6 files changed, 46 insertions(+), 56 deletions(-) delete mode 100644 test/ambiguities.jl create mode 100644 test/aqua.jl diff --git a/Project.toml b/Project.toml index 494f68f55b..ff3dcae1fd 100644 --- a/Project.toml +++ b/Project.toml @@ -94,4 +94,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" [targets] -test = ["Test", "BoundaryValueDiffEq", "Colors", "DiffEqCallbacks", "DoubleFloats", "FiniteDifferences", "Gtk", "ImageIO", "ImageMagick", "OrdinaryDiffEq", "NLsolve", "Plots", "PythonPlot", "Quaternions", "QuartzImageIO", "RecipesBase"] +test = ["Test", "BoundaryValueDiffEq", "Colors", "DiffEqCallbacks", "DoubleFloats", "FiniteDifferences", "Gtk", "ImageIO", "ImageMagick", "OrdinaryDiffEq", "NLsolve", "Plots", "PythonPlot", "Quaternions", "QuartzImageIO", "RecipesBase", "TestEnv"] diff --git a/src/utils/features.jl b/src/utils/features.jl index d608283a25..8104a6ac3d 100644 --- a/src/utils/features.jl +++ b/src/utils/features.jl @@ -153,6 +153,11 @@ function find_manifold_retractions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) return retr_features end +@doc raw""" + find_manifold_inverse_retractions(M; kwargs...) + +Find inverse retractions that are available on a manifold by trying to call them. +""" function find_manifold_inverse_retractions(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) checks = AbstractInverseRetractionMethod[] # The following can only be checked on certain manifolds and/or need parameters @@ -184,7 +189,7 @@ function find_manifold_inverse_retractions(M; p=rand(M), X=rand(M; vector_at=p), ) end # - # Ok – Let's check them + # After we collected all possible ones, let check whether they exist inv_retr_features = AbstractInverseRetractionMethod[] for inv_retr in checks try @@ -196,6 +201,11 @@ function find_manifold_inverse_retractions(M; p=rand(M), X=rand(M; vector_at=p), return inv_retr_features end +@doc raw""" + find_manifold_inverse_retractions(M; kwargs...) + +Find vector transports that are available on a manifold by trying to call them. +""" function find_manifold_vector_transports(M; p=rand(M), X=rand(M; vector_at=p), t=1.0) checks = AbstractVectorTransportMethod[] # The following can only be checked on certain manifolds and/or need parameters @@ -237,6 +247,7 @@ end """ find_manifold_properties(M) +Find properties of a manifold, that are not related to a function. """ function find_manifold_properties(M::AbstractManifold) properties = Dict{Symbol,<:Any}() @@ -325,8 +336,9 @@ function show(io::IO, mf::ManifoldFeatures) end function show(io::IO, ::MIME"test/markdown", mf::ManifoldFeatures) - # Print the features in a nice Markdown table for the docs + # TODO: Print the features in a nice Markdown table for the docs end + @doc """ ManifoldExpectations @@ -366,13 +378,11 @@ end For a current set of features and expectations infer whether they provide information for a symbol `s`. -For example for `has_feature_expectations(F,E,:exp)` to return `true` +For example for `has_feature_expectations(F,E, exp)` to return `true` * the function `exp` has to be in the features * the expectations have to have a tolerance present """ -function has_feature_expectations(F::ManifoldFeatures, E::ManifoldExpectations, s::Symbol) - # default cases - f = getfield(Main, s) #default: is test a function name or a type? +function has_feature_expectations(F::ManifoldFeatures, E::ManifoldExpectations, f) (f isa Function) && (f in F.functions) && (return true) (f isa AbstractRetractionMethod) && (f in F.retractions) && (return true) (f isa AbstractInverseRetractionMethod) && (f in F.retractions) && (return true) diff --git a/test/ambiguities.jl b/test/ambiguities.jl deleted file mode 100644 index 4214a4feff..0000000000 --- a/test/ambiguities.jl +++ /dev/null @@ -1,47 +0,0 @@ -include("header.jl") -""" - has_type_in_signature(sig, T::Type) - -Test whether the signature `sig` has an argument of type `T` as one of its parameters. -""" -function has_type_in_signature(sig, T::Type) - return any(map(Base.unwrap_unionall(sig.sig).parameters) do x - xw = Base.rewrap_unionall(x, sig.sig) - return (xw isa Type ? xw : xw.T) <: T - end) -end - -@testset "Ambiguities" begin - if VERSION.prerelease == () && !Sys.iswindows() && VERSION < v"1.11.0" - mbs = Test.detect_ambiguities(ManifoldsBase) - # Interims solution until we follow what was proposed in - # https://discourse.julialang.org/t/avoid-ambiguities-with-individual-number-element-identity/62465/2 - fmbs = filter(x -> !any(has_type_in_signature.(x, Identity)), mbs) - FMBS_LIMIT = 34 - println("Number of ManifoldsBase.jl ambiguities: $(length(fmbs))") - @test length(fmbs) <= FMBS_LIMIT - if length(fmbs) > FMBS_LIMIT - for amb in fmbs - println(amb) - println() - end - end - ms = Test.detect_ambiguities(Manifolds) - # Interims solution until we follow what was proposed in - # https://discourse.julialang.org/t/avoid-ambiguities-with-individual-number-element-identity/62465/2 - fms = filter(x -> !any(has_type_in_signature.(x, Identity)), ms) - FMS_LIMIT = 47 - println("Number of Manifolds.jl ambiguities: $(length(fms))") - if length(fms) > FMS_LIMIT - for amb in fms - println(amb) - println() - end - end - @test length(fms) <= FMS_LIMIT - # this test takes way too long to perform regularly - # @test length(our_base_ambiguities()) <= 4 - else - @info "Skipping Ambiguity tests for pre-release versions" - end -end diff --git a/test/aqua.jl b/test/aqua.jl new file mode 100644 index 0000000000..18d7f8d3f9 --- /dev/null +++ b/test/aqua.jl @@ -0,0 +1,23 @@ +using Aqua, Manifolds, Test + +@testset "Aqua.jl" begin + Aqua.test_all( + Manifolds; + ambiguities=( + exclude=[ + *, + ==, + allocate_result, + inv, + inv!, + mean, + Manifolds.mul!, + reshape, + Manifolds.SemidirectProductOperation, + setindex!, + Manifolds.Manifolds.TranslationGroup, + ], + broken=false, + ), + ) +end diff --git a/test/run_legacy_tests.jl b/test/run_legacy_tests.jl index e2612aa96f..e459d7d6a5 100644 --- a/test/run_legacy_tests.jl +++ b/test/run_legacy_tests.jl @@ -13,7 +13,7 @@ end @testset "Manifolds.jl" begin if TEST_GROUP ∈ ["all", "test_manifolds"] include_test("differentiation.jl") - include_test("ambiguities.jl") + # include_test("ambiguities.jl") # -> moved to Aqua.jl include_test("test_deprecated.jl") @testset "utils test" begin Random.seed!(42) @@ -223,4 +223,4 @@ end if TEST_GROUP ∈ ["all", "test_integration"] && !Sys.isapple() include_test("recipes.jl") end -end \ No newline at end of file +end diff --git a/test/runtests.jl b/test/runtests.jl index d88911e708..cd453742d8 100755 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -76,5 +76,9 @@ if (abspath(PROGRAM_FILE) == @__FILE__) # Run as script #...then continue with the test run in normal mode below end include("header.jl") +include("aqua.jl") +# new tests here + +# run old / legacy tests include("run_legacy_tests.jl") From c52395d4254018d111e97725a223915a24419ac2 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 11:37:11 +0200 Subject: [PATCH 07/13] add more compats. --- Project.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Project.toml b/Project.toml index ff3dcae1fd..5fa0efa5bc 100644 --- a/Project.toml +++ b/Project.toml @@ -45,6 +45,7 @@ ManifoldsTestExt = "Test" [compat] BoundaryValueDiffEq = "4, 5.6.1" Colors = "0.12" +DiffEqCallbacks = "3.6" Distributions = "0.22.6, 0.23, 0.24, 0.25" Einsum = "0.4" Graphs = "1.4" @@ -56,6 +57,7 @@ ManifoldsBase = "0.15.8" Markdown = "1.6" MatrixEquations = "2.2" NonlinearSolve = "1 - 3.7.3, 3.8.3" +NLsolve = "4.5" OrdinaryDiffEq = "6.31" Plots = "1" PythonCall = "0.9" @@ -69,6 +71,7 @@ SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" StaticArrays = "1.4.3" Statistics = "1.6" StatsBase = "0.32, 0.33, 0.34" +Test = "1.6" julia = "1.6" [extras] From caf68e3a3909ce4683ed6dc30c20bd5c3b9c9ea2 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 11:59:44 +0200 Subject: [PATCH 08/13] Narrow down the list of type piracy functions/types. --- Project.toml | 1 + test/aqua.jl | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 5fa0efa5bc..2c310e34ba 100644 --- a/Project.toml +++ b/Project.toml @@ -50,6 +50,7 @@ Distributions = "0.22.6, 0.23, 0.24, 0.25" Einsum = "0.4" Graphs = "1.4" HybridArrays = "0.4" +InteractiveUtils = "1.6" Kronecker = "0.4, 0.5" LinearAlgebra = "1.6" ManifoldDiff = "0.3.7" diff --git a/test/aqua.jl b/test/aqua.jl index 18d7f8d3f9..15cb6447a7 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -1,4 +1,4 @@ -using Aqua, Manifolds, Test +using Aqua, Manifolds, Test, StaticArrays # Last package temporary @testset "Aqua.jl" begin Aqua.test_all( @@ -15,9 +15,24 @@ using Aqua, Manifolds, Test reshape, Manifolds.SemidirectProductOperation, setindex!, - Manifolds.Manifolds.TranslationGroup, + Manifolds.TranslationGroup, + Manifolds.GroupManifold, ], broken=false, ), + piracies=( + treat_as_own=[ + AbstractDecoratorManifold, # MAybe fix? + AbstractManifold, # Maybe fix? + AbstractNumbers, # Maybe fix? + ProductManifold, # Maybe fix? + ExtrinsicEstimation, # already deprecated + Manifolds.EmptyTrait, # Maybe fix? + Manifolds.TraitList, # Maybe fix? + Manifolds.GeodesicInterpolationWithinRadius, # Probably fix + # StaticArray, # Definetly fix! + allocate, # Maybe fix + ], + ), ) end From b89cf8ba7b8a456d04a1f59c9aecad5ba77800fb Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 12:55:28 +0200 Subject: [PATCH 09/13] Resolve all but one Aqua error. --- Project.toml | 9 +++++++++ src/manifolds/Tucker.jl | 4 ++-- test/aqua.jl | 1 - 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 2c310e34ba..901c013b64 100644 --- a/Project.toml +++ b/Project.toml @@ -47,9 +47,14 @@ BoundaryValueDiffEq = "4, 5.6.1" Colors = "0.12" DiffEqCallbacks = "3.6" Distributions = "0.22.6, 0.23, 0.24, 0.25" +DoubleFloats = "1.3" Einsum = "0.4" +FiniteDifferences = "0.12" Graphs = "1.4" +Gtk = "1.3" HybridArrays = "0.4" +ImageIO = "0.6" +ImageMagick = "1.3" InteractiveUtils = "1.6" Kronecker = "0.4, 0.5" LinearAlgebra = "1.6" @@ -62,6 +67,8 @@ NLsolve = "4.5" OrdinaryDiffEq = "6.31" Plots = "1" PythonCall = "0.9" +PythonPlot = "1" +QuartzImageIO = "0.7" Quaternions = "0.5, 0.6, 0.7" Random = "1.6" RecipesBase = "1.1" @@ -73,6 +80,8 @@ StaticArrays = "1.4.3" Statistics = "1.6" StatsBase = "0.32, 0.33, 0.34" Test = "1.6" +TestEnv = "1" +VisualRegressionTests = "1.3" julia = "1.6" [extras] diff --git a/src/manifolds/Tucker.jl b/src/manifolds/Tucker.jl index 87e22e03be..15b6cce8e1 100644 --- a/src/manifolds/Tucker.jl +++ b/src/manifolds/Tucker.jl @@ -98,8 +98,8 @@ struct TuckerPoint{T,D} <: AbstractManifoldPoint end function TuckerPoint( core::AbstractArray{T,D}, - factors::Vararg{MtxT,D}, -) where {T,D,MtxT<:AbstractMatrix{T}} + factors::Vararg{<:AbstractMatrix{T},D}, +) where {T,D} # Take the QR decompositions of the factors and multiply the R factors into the core qrfacs = qr.(factors) Q = map(qrfac -> qrfac.Q, qrfacs) diff --git a/test/aqua.jl b/test/aqua.jl index 15cb6447a7..cea5ea0e9a 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -16,7 +16,6 @@ using Aqua, Manifolds, Test, StaticArrays # Last package temporary Manifolds.SemidirectProductOperation, setindex!, Manifolds.TranslationGroup, - Manifolds.GroupManifold, ], broken=false, ), From 63a393df358d2afb82b5656c0db8cbe719db1cab Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 13:07:08 +0200 Subject: [PATCH 10/13] Update Badges. --- NEWS.md | 8 +++++++- README.md | 16 +++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 23af11c3d3..76d7db22b4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.9.17] – unreleased +## [0.9.18] – + +### Changed + +* Moved ambiguity testing to Aqua.jl + +## [0.9.17] – 2024-04-23 ### Added diff --git a/README.md b/README.md index 8cebb371b0..52ec6dcbcf 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,18 @@ -| **Documentation** | **Source** | **Citation** | -|:-----------------:|:----------------------:|:------------:| -| [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliamanifolds.github.io/Manifolds.jl/stable/) | [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) | [![arXiv](https://img.shields.io/badge/arXiv%20CS.MS-2106.08777-blue.svg)](https://arxiv.org/abs/2106.08777) | -| [![](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliamanifolds.github.io/Manifolds.jl/latest/) | [![CI](https://github.com/JuliaManifolds/Manifolds.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/JuliaManifolds/Manifolds.jl/actions?query=workflow%3ACI+branch%3Amaster) | [![DOI](https://zenodo.org/badge/190447542.svg)](https://zenodo.org/badge/latestdoi/190447542) | -| | [![codecov.io](http://codecov.io/github/JuliaManifolds/Manifolds.jl/coverage.svg?branch=master)](https://codecov.io/gh/JuliaManifolds/Manifolds.jl/) | +[![docs stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliamanifolds.github.io/Manifolds.jl/stable/) +[![docs dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://juliamanifolds.github.io/Manifolds.jl/latest/) [![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) +[![CI](https://github.com/JuliaManifolds/Manifolds.jl/actions/workflows/ci.yml/badge.svg)](https://github.com/JuliaManifolds/Manifolds.jl/actions?query=workflow%3ACI+branch%3Amaster) +[![codecov.io](http://codecov.io/github/JuliaManifolds/Manifolds.jl/coverage.svg?branch=master)](https://codecov.io/gh/JuliaManifolds/Manifolds.jl/) +[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) +[![ACM TOMS](https://img.shields.io/badge/ACM%20TOMS-10.1145%2F3618296-blue.svg)](http://doi.org/10.1145/3618296) +[![DOI](https://zenodo.org/badge/190447542.svg)](https://zenodo.org/badge/latestdoi/190447542) + + +https://img.shields.io/badge/arXiv%20CS.MS-2106.08777-blue.svg +https://img.shields.io/badge/AC%20TOMS-10.1145%2F3618296-blue.svg Package __Manifolds.jl__ aims to provide both a unified interface to define and use manifolds as well as a library of manifolds to use for your projects. This package is mostly stable, see https://github.com/JuliaManifolds/Manifolds.jl/issues/438 for planned upcoming changes. From 531fb20ec90941dd9f38595b5c2f68f3cb3d1d7b Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 13:54:11 +0200 Subject: [PATCH 11/13] Remove two spurious links. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 52ec6dcbcf..55f38b59db 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,6 @@ [![ACM TOMS](https://img.shields.io/badge/ACM%20TOMS-10.1145%2F3618296-blue.svg)](http://doi.org/10.1145/3618296) [![DOI](https://zenodo.org/badge/190447542.svg)](https://zenodo.org/badge/latestdoi/190447542) - -https://img.shields.io/badge/arXiv%20CS.MS-2106.08777-blue.svg -https://img.shields.io/badge/AC%20TOMS-10.1145%2F3618296-blue.svg Package __Manifolds.jl__ aims to provide both a unified interface to define and use manifolds as well as a library of manifolds to use for your projects. This package is mostly stable, see https://github.com/JuliaManifolds/Manifolds.jl/issues/438 for planned upcoming changes. From 569725cc9c6a5d279a3634393e0ef2a165333428 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 15:52:26 +0200 Subject: [PATCH 12/13] Collect all but a few strange ambiguities (StatsBase? Broadcast? A constructor?) --- test/aqua.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/aqua.jl b/test/aqua.jl index cea5ea0e9a..4e9d970a40 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -7,15 +7,27 @@ using Aqua, Manifolds, Test, StaticArrays # Last package temporary exclude=[ *, ==, + allocate, + Manifolds.allocate_coordinates, allocate_result, + check_point, + copyto!, + compose, + compose!, + get_coordinates, + get_coordinates!, + get_vector, + get_vector!, + getindex, inv, inv!, mean, Manifolds.mul!, reshape, - Manifolds.SemidirectProductOperation, + similar, setindex!, - Manifolds.TranslationGroup, + view, + Manifolds.AbstractGroupOperation, ], broken=false, ), From 45e005124d325b73065e7cec43f61cad0c984b16 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Mon, 29 Apr 2024 15:53:25 +0200 Subject: [PATCH 13/13] Reduce a compat. --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 901c013b64..cfa89c2707 100644 --- a/Project.toml +++ b/Project.toml @@ -57,7 +57,7 @@ ImageIO = "0.6" ImageMagick = "1.3" InteractiveUtils = "1.6" Kronecker = "0.4, 0.5" -LinearAlgebra = "1.6" +LinearAlgebra = "1" ManifoldDiff = "0.3.7" ManifoldsBase = "0.15.8" Markdown = "1.6"