From afb786a20ad85330694598fdf4a12a4a8189220b Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Tue, 28 Jan 2025 10:00:05 +0100 Subject: [PATCH 01/10] Implement fast equality for toric varieties The only computationally complex part is computing the rays of the toric varieties, which are usually already computed anyway. --- .../NormalToricVarieties/constructors.jl | 13 ++++++++++--- .../ToricVarieties/normal_toric_varieties.jl | 10 +++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index bcfdbc6165a5..8aac7d5a85f4 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -181,9 +181,16 @@ end # Equality ###################### -function Base.:(==)(tv1::NormalToricVariety, tv2::NormalToricVariety) - tv1 === tv2 && return true - error("Equality of normal toric varieties is computationally very demanding. More details in the documentation.") +function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) + X === Y && return true + ambient_dim(X) == ambient_dim(Y) || return false + f_vector(X) == f_vector(Y) || return false + + # p is a permutation such that the i-th ray of X is the p(i)-th ray of Y + p = inv(perm(sortperm(rays(X)))) * perm(sortperm(rays(Y))) + + @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] + return Set(map(r -> Set(p.(r)), rows(X))) == Set(rows(Y)) end function Base.hash(tv::NormalToricVariety, h::UInt) diff --git a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl index a4432e9365f1..89fb69cce6a9 100644 --- a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl +++ b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl @@ -41,6 +41,14 @@ @testset "Equality of normal toric varieties" begin @test (p2 === f2) == false @test p2 === p2 - @test_throws ErrorException("Equality of normal toric varieties is computationally very demanding. More details in the documentation.") p2 == f2 + @test p2 != f2 + + X = projective_space(NormalToricVariety, 2) + X = domain(blow_up(X, [3, 4])) + X = domain(blow_up(X, [-2, -3])) + Y = weighted_projective_space(NormalToricVariety, [1, 2, 3]) + Y = domain(blow_up(Y, [-1, -1])) + Y = domain(blow_up(Y, [3, 4])) + @test X == Y end end From 2c7bba6ba74ef64ec79b150420f19bc002c33b7f Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Tue, 28 Jan 2025 10:06:15 +0100 Subject: [PATCH 02/10] Update docs --- .../ToricVarieties/NormalToricVarieties.md | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md b/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md index 230fbfcdac93..eea89e99ccda 100644 --- a/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md +++ b/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md @@ -18,31 +18,10 @@ the affine and non-affine case: ## Equality of Normal Toric Varieties -!!! warning - Equality `==` of normal toric varieties currently checks equality of - memory locations. We recommend using `===`, which always checks - equality of memory locations in OSCAR. - -To check that the fans considered as sets of cones are equal, you may use the method below: - -```julia -function slow_equal(tv1::NormalToricVariety, tv2::NormalToricVariety) - tv1 === tv2 && return true - ambient_dim(tv1) == ambient_dim(tv2) || return false - f_vector(tv1) == f_vector(tv2) || return false - return Set(maximal_cones(tv1)) == Set(maximal_cones(tv2)) -end -``` - -Polyhedral fans can be stored in Polymake using either rays or -hyperplanes. In the former case, cones are stored as sets of indices of -rays, corresponding to polyhedral hulls, while in the latter case, cones -are stored as sets of indices of hyperplanes, corresponding to -intersections of hyperplanes. Converting between these two formats can -be expensive. In the case where the polyhedral fans of two normal toric -varieties are both stored using rays, the above method `slow_equal` has -computational complexity $O(n \log n)$, where $n$ is the number of -cones. +Equality `==` of normal toric varieties checks equality of the +polyhedral fans (sets of cones). This computes the rays of both of the +toric varieties, which can be expensive if they are not already computed. +Triple-equality `===` always checks equality of memory locations in OSCAR. ## Constructors From 025f515a4be6ea827c535e34f478039595aa596b Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Tue, 28 Jan 2025 18:31:21 +0100 Subject: [PATCH 03/10] Fix equality check, add test, fix hash Also remove `f_vector`. --- .../ToricVarieties/NormalToricVarieties/constructors.jl | 9 ++++++--- .../ToricVarieties/normal_toric_varieties.jl | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index 8aac7d5a85f4..80609b0f540b 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -184,17 +184,20 @@ end function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) X === Y && return true ambient_dim(X) == ambient_dim(Y) || return false - f_vector(X) == f_vector(Y) || return false + n_rays(X) == n_rays(Y) || return false # p is a permutation such that the i-th ray of X is the p(i)-th ray of Y p = inv(perm(sortperm(rays(X)))) * perm(sortperm(rays(Y))) + for i in 1:n_rays(X) + rays(X)[i] == rays(Y)[p(i)] || return false + end @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] return Set(map(r -> Set(p.(r)), rows(X))) == Set(rows(Y)) end -function Base.hash(tv::NormalToricVariety, h::UInt) - return hash(objectid(tv), h) +function Base.hash(X::NormalToricVariety, h::UInt) + return hash(ambient_dim(X), h) end diff --git a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl index 89fb69cce6a9..0ad402b68133 100644 --- a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl +++ b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl @@ -50,5 +50,10 @@ Y = domain(blow_up(Y, [-1, -1])) Y = domain(blow_up(Y, [3, 4])) @test X == Y + + Z = projective_space(NormalToricVariety, 2) + X = domain(blow_up(Z, [1, 1])) + Y = domain(blow_up(Z, [1, 2])) + @test X != Y end end From e36eed609250772e66af0f348a267999cce04a9c Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Wed, 29 Jan 2025 15:46:17 +0100 Subject: [PATCH 04/10] Add documentation, improve hash function --- .../NormalToricVarieties/constructors.jl | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index 80609b0f540b..f5e17298eae8 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -181,6 +181,23 @@ end # Equality ###################### +@doc raw""" + (==)(X::NormalToricVariety, Y::NormalToricVariety) + +Checks equality of the polyhedral fans as sets of cones. + +# Examples +```jldoctest +julia> H = hirzebruch_surface(NormalToricVariety, 0) +Normal toric variety + +julia> P1 = projective_space(NormalToricVariety, 1) +Normal toric variety + +julia> H == P1 * P1 +true +``` +""" function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) X === Y && return true ambient_dim(X) == ambient_dim(Y) || return false @@ -196,11 +213,40 @@ function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) return Set(map(r -> Set(p.(r)), rows(X))) == Set(rows(Y)) end -function Base.hash(X::NormalToricVariety, h::UInt) - return hash(ambient_dim(X), h) -end +@doc raw""" + hash(X::NormalToricVariety, h::UInt) +Computes a hash of a normal toric variety `X` with the property that, outside of hash collisions, `X_1 == X_2` if and only if `hash(X_1) == hash(X_2)`. +# Examples +```jldoctest +julia> ray_generators = [[1,0], [1, 1]] +2-element Vector{Vector{Int64}}: + [1, 0] + [0, 1] + +julia> max_cones = incidence_matrix([[1, 2]]) +1×2 IncidenceMatrix +[1, 2] + + +julia> X = normal_toric_variety(max_cones, ray_generators) +Normal toric variety + +julia> Y = affine_space(NormalToricVariety, 2) +Normal toric variety + +julia> hash(X) == hash(Y) +false +``` +""" +function Base.hash(X::NormalToricVariety, h::UInt) + p = inv(perm(sortperm(rays(X)))) + @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] + set_of_maximal_cones = Set(map(r -> Set(p.(r)), rows(X))) + sorted_rays = Vector.([rays(X)[p(i)] for i in 1:n_rays(X)]) + return hash((sorted_rays, set_of_maximal_cones), h) +end ###################### # Display From 2280d04451122e2e209501b58988076b6ea2fb3e Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Wed, 29 Jan 2025 16:24:47 +0100 Subject: [PATCH 05/10] Commented out the hash docstring --- .../NormalToricVarieties/constructors.jl | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index f5e17298eae8..5cbd18989df3 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -213,33 +213,11 @@ function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) return Set(map(r -> Set(p.(r)), rows(X))) == Set(rows(Y)) end -@doc raw""" - hash(X::NormalToricVariety, h::UInt) - -Computes a hash of a normal toric variety `X` with the property that, outside of hash collisions, `X_1 == X_2` if and only if `hash(X_1) == hash(X_2)`. - -# Examples -```jldoctest -julia> ray_generators = [[1,0], [1, 1]] -2-element Vector{Vector{Int64}}: - [1, 0] - [0, 1] - -julia> max_cones = incidence_matrix([[1, 2]]) -1×2 IncidenceMatrix -[1, 2] - - -julia> X = normal_toric_variety(max_cones, ray_generators) -Normal toric variety - -julia> Y = affine_space(NormalToricVariety, 2) -Normal toric variety - -julia> hash(X) == hash(Y) -false -``` -""" +# @doc raw""" +# hash(X::NormalToricVariety, h::UInt) +# +# Computes a hash of a normal toric variety `X` with the property that, outside of hash collisions, `X_1 == X_2` if and only if `hash(X_1) == hash(X_2)`. +# """ function Base.hash(X::NormalToricVariety, h::UInt) p = inv(perm(sortperm(rays(X)))) @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] From 4c4fb078b3dd218ce092ba865b18f77024e2e5b5 Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Wed, 29 Jan 2025 17:31:38 +0100 Subject: [PATCH 06/10] Use permuted --- .../ToricVarieties/NormalToricVarieties/constructors.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index 5cbd18989df3..b34f27b6216b 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -222,7 +222,7 @@ function Base.hash(X::NormalToricVariety, h::UInt) p = inv(perm(sortperm(rays(X)))) @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] set_of_maximal_cones = Set(map(r -> Set(p.(r)), rows(X))) - sorted_rays = Vector.([rays(X)[p(i)] for i in 1:n_rays(X)]) + sorted_rays = Vector.(permuted(collect(rays(X)), inv(p))) return hash((sorted_rays, set_of_maximal_cones), h) end From 46621133d1056128afe01bfaf505385b31ff504d Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Wed, 29 Jan 2025 17:54:46 +0100 Subject: [PATCH 07/10] Fix inv(p), add hash tests --- .../ToricVarieties/NormalToricVarieties/constructors.jl | 6 ++++-- .../ToricVarieties/normal_toric_varieties.jl | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index b34f27b6216b..85790edfbbfb 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -216,13 +216,15 @@ end # @doc raw""" # hash(X::NormalToricVariety, h::UInt) # -# Computes a hash of a normal toric variety `X` with the property that, outside of hash collisions, `X_1 == X_2` if and only if `hash(X_1) == hash(X_2)`. +# Computes a hash of a normal toric variety `X` with the property that, +# outside of hash collisions, `X_1 == X_2` if and only if +# `hash(X_1) == hash(X_2)`. # """ function Base.hash(X::NormalToricVariety, h::UInt) p = inv(perm(sortperm(rays(X)))) @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] set_of_maximal_cones = Set(map(r -> Set(p.(r)), rows(X))) - sorted_rays = Vector.(permuted(collect(rays(X)), inv(p))) + sorted_rays = Vector.(permuted(collect(rays(X)), p)) return hash((sorted_rays, set_of_maximal_cones), h) end diff --git a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl index 0ad402b68133..3e2ba3284858 100644 --- a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl +++ b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl @@ -55,5 +55,12 @@ X = domain(blow_up(Z, [1, 1])) Y = domain(blow_up(Z, [1, 2])) @test X != Y + + H = hirzebruch_surface(NormalToricVariety, 0) + P1 = projective_space(NormalToricVariety, 1) + ray_generators = [[1, 1], [1, 2]] + max_cones = incidence_matrix([[1, 2]]) + X = normal_toric_variety(max_cones, ray_generators) + @test length(Set(hash.([H, P1 * P1, X]))) == 2 end end From 985293ee57cb778d2c8f10239faf16ba2def9c92 Mon Sep 17 00:00:00 2001 From: Erik Paemurru <143521159+paemurru@users.noreply.github.com> Date: Wed, 29 Jan 2025 18:25:03 +0100 Subject: [PATCH 08/10] Update test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl Co-authored-by: Benjamin Lorenz --- test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl index 3e2ba3284858..2502fcc25ca0 100644 --- a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl +++ b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl @@ -61,6 +61,6 @@ ray_generators = [[1, 1], [1, 2]] max_cones = incidence_matrix([[1, 2]]) X = normal_toric_variety(max_cones, ray_generators) - @test length(Set(hash.([H, P1 * P1, X]))) == 2 + @test length(Set([H, P1 * P1, X])) == 2 end end From 04078e663d1b3074553ef37a856d2c38379f5dfc Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Sat, 1 Feb 2025 10:33:16 +0100 Subject: [PATCH 09/10] Remove docstring from the hash function Also add polymake code to check whether rays are computed into the documentation. --- .../ToricVarieties/NormalToricVarieties.md | 7 ++- .../NormalToricVarieties/constructors.jl | 48 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md b/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md index eea89e99ccda..93f47153d9ff 100644 --- a/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md +++ b/docs/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties.md @@ -19,8 +19,11 @@ the affine and non-affine case: ## Equality of Normal Toric Varieties Equality `==` of normal toric varieties checks equality of the -polyhedral fans (sets of cones). This computes the rays of both of the -toric varieties, which can be expensive if they are not already computed. +corresponding polyhedral fans as sets of cones. +This computes the rays of both of the toric varieties, which can be +expensive if they are not already computed, meaning if +`"RAYS" in Polymake.list_properties(Oscar.pm_object(polyhedral_fan(X)))` +is false for one of the varieties. Triple-equality `===` always checks equality of memory locations in OSCAR. diff --git a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl index 85790edfbbfb..e1e5cfe3681f 100644 --- a/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl +++ b/src/AlgebraicGeometry/ToricVarieties/NormalToricVarieties/constructors.jl @@ -182,7 +182,7 @@ end ###################### @doc raw""" - (==)(X::NormalToricVariety, Y::NormalToricVariety) + (==)(X::NormalToricVariety, Y::NormalToricVariety) -> Bool Checks equality of the polyhedral fans as sets of cones. @@ -209,23 +209,45 @@ function Base.:(==)(X::NormalToricVariety, Y::NormalToricVariety) for i in 1:n_rays(X) rays(X)[i] == rays(Y)[p(i)] || return false end - @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] + @inline rows(Z) = [ + row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z) + ] return Set(map(r -> Set(p.(r)), rows(X))) == Set(rows(Y)) end -# @doc raw""" -# hash(X::NormalToricVariety, h::UInt) -# -# Computes a hash of a normal toric variety `X` with the property that, -# outside of hash collisions, `X_1 == X_2` if and only if -# `hash(X_1) == hash(X_2)`. -# """ -function Base.hash(X::NormalToricVariety, h::UInt) +@doc raw""" + _id(X::NormalToricVariety) + -> Tuple{Vector{Vector{QQFieldElem}}, Vector{Vector{Int64}}} + +Given a toric variety `X`, returns a pair `Oscar._id(X)` with the +following property: two toric varieties `X` and `Y` have equal +polyhedral fans, taken as sets of cones, if and only if +`Oscar._id(X) == Oscar._id(Y)`. + +# Examples +```jldoctest +julia> H = hirzebruch_surface(NormalToricVariety, 0) +Normal toric variety + +julia> P1 = projective_space(NormalToricVariety, 1) +Normal toric variety + +julia> Oscar._id(H) == Oscar._id(P1 * P1) +true +``` +""" +function _id(X::NormalToricVariety) p = inv(perm(sortperm(rays(X)))) - @inline rows(Z) = [row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z)] - set_of_maximal_cones = Set(map(r -> Set(p.(r)), rows(X))) sorted_rays = Vector.(permuted(collect(rays(X)), p)) - return hash((sorted_rays, set_of_maximal_cones), h) + @inline rows(Z) = [ + row(maximal_cones(IncidenceMatrix, Z), i) for i in 1:n_maximal_cones(Z) + ] + sorted_maximal_cones = sort(map(r -> sort(Vector(p.(r))), rows(X))) + return (sorted_rays, sorted_maximal_cones) +end + +function Base.hash(X::NormalToricVariety, h::UInt) + return hash(_id(X), h) end ###################### From 5113dab11b5685f92327c6a2d2069cc029ba48cf Mon Sep 17 00:00:00 2001 From: Erik Paemurru Date: Sat, 1 Feb 2025 12:26:26 +0100 Subject: [PATCH 10/10] Speed test --- .../ToricVarieties/normal_toric_varieties.jl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl index 2502fcc25ca0..f76966c5fa0f 100644 --- a/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl +++ b/test/AlgebraicGeometry/ToricVarieties/normal_toric_varieties.jl @@ -62,5 +62,22 @@ max_cones = incidence_matrix([[1, 2]]) X = normal_toric_variety(max_cones, ray_generators) @test length(Set([H, P1 * P1, X])) == 2 + + @testset "Speed test hash (at most 0.5 seconds)" begin + success = false + ntv5 = normal_toric_variety(polarize(polyhedron(Polymake.polytope.rand_sphere(5, 60; seed=42)))) + hash(ntv5) + for i in 1:5 + stats = @timed hash(ntv5) + duration = stats.time - stats.gctime + if duration < 0.5 + success = true + break + else + @warn "Hash took $duration > 0.5 seconds (i=$i)" + end + end + @test success == true + end end end