From 97f88af11c8665ad1a8c86f13db668cc3c37695b Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Tue, 18 May 2021 21:10:17 +0200 Subject: [PATCH 1/7] add _max_alphabet_length this function guards against alphabets too long for given word type --- src/abstract_words.jl | 2 ++ src/rewriting.jl | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/abstract_words.jl b/src/abstract_words.jl index ff598d5c..cca7ab1d 100644 --- a/src/abstract_words.jl +++ b/src/abstract_words.jl @@ -122,3 +122,5 @@ function Base.show(io::IO, w::AbstractWord{T}) where T join(io, w, "·") end end + +_max_alphabet_length(::Type{<:AbstractWord{T}}) where T = typemax(T) diff --git a/src/rewriting.jl b/src/rewriting.jl index d7d80881..3cdf6b63 100644 --- a/src/rewriting.jl +++ b/src/rewriting.jl @@ -68,6 +68,8 @@ end function RewritingSystem(rwrules::Vector{Pair{W,W}}, order::O; bare=false) where {W<:AbstractWord, O<:WordOrdering} + @assert length(alphabet(order)) <= _max_alphabet_length(W) "Type $W can not store words over $(alphabet(order))." + rls = if !bare abt_rules = rules(W, alphabet(order)) [abt_rules; rwrules] From d092c8025cf7f46baabfe60c1835c13ce4bea556 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Tue, 18 May 2021 21:12:03 +0200 Subject: [PATCH 2/7] add rewrite_from_left with A::Alphabet which does free rewrite --- src/rewriting.jl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/rewriting.jl b/src/rewriting.jl index 3cdf6b63..76f1d41b 100644 --- a/src/rewriting.jl +++ b/src/rewriting.jl @@ -38,6 +38,27 @@ function rewrite_from_left!( return v end +""" + rewrite_from_left!(v::AbstractWord, w::AbstractWord, A::Alphabet) +Append `w` to `v` applying free reductions as defined by the inverses of `A`. +""" +function rewrite_from_left!(v::AbstractWord, w::AbstractWord, A::Alphabet) + while !isone(w) + if isone(v) + push!(v, popfirst!(w)) + else + # the first check is for monoids only + if hasinverse(last(v), A) && inv(A, last(v)) == first(w) + pop!(v) + popfirst!(w) + else + push!(v, popfirst!(w)) + end + end + end + return v +end + """ AbstractRewritingSystem{W,O} Abstract type representing rewriting system. From cf537858cdf2ff8634cdf24751fc8a67cc7fbaf9 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Sun, 23 May 2021 16:19:40 +0200 Subject: [PATCH 3/7] rework Alphabet --- src/alphabets.jl | 118 +++++++++++++++++----------------------------- test/alphabets.jl | 16 +++---- test/rewriting.jl | 2 +- 3 files changed, 50 insertions(+), 86 deletions(-) diff --git a/src/alphabets.jl b/src/alphabets.jl index e326e053..df11e10d 100644 --- a/src/alphabets.jl +++ b/src/alphabets.jl @@ -1,16 +1,13 @@ """ - mutable struct Alphabet{T} + struct Alphabet{T} A basic struct for storing alphabets. An alphabet consists of the symbols of a common type `T`. # Example ```julia-repl -julia> Alphabet() +julia> Alphabet{Char}() Empty alphabet of Char -julia> Alphabet{String}() -Empty alphabet of String - julia> Alphabet(["a", "b", "c"]) Alphabet of String: 1. "a" @@ -18,28 +15,21 @@ Alphabet of String: 3. "c" ``` """ -mutable struct Alphabet{T} +struct Alphabet{T} letters::Vector{T} inversions::Vector{Int} - function Alphabet{T}(init::Vector{T} = Vector{T}(); safe = true) where T - if safe && T <: Integer - error("I am sorry to say that, but it is not allowed for alphabet symbols to be integers. If you do *really* know what you are doing, call the constructor with `safe = false`.") - end - if length(unique(init)) != length(init) - error("Init vector contains non-unique symbols.") - end - new(init, fill(0, length(init))) - end - function Alphabet(letters::Vector{T}, inversions::Vector{Int}) where T - @assert !(T<:Integer) "Alphabet over a set of integers is not supported." + function Alphabet(letters::AbstractVector{T}, inversions::AbstractVector{<:Integer}) where T + @assert length(unique(letters)) == length(letters) "Non-unique set of letters" + @assert !(T<:Integer) "Alphabets of integers are not supported." @assert length(letters) == length(inversions) + @assert all(i-> 0 ≤ i ≤ length(letters), inversions) return new{T}(letters, inversions) end end -Alphabet() = Alphabet{Char}() -Alphabet(x::Vector{T}; safe = true) where T = Alphabet{T}(x; safe = safe) +Alphabet{T}() where T = Alphabet(T[]) +Alphabet(init::AbstractVector{T}) where T = Alphabet(init, fill(0, length(init))) letters(A::Alphabet) = A.letters @@ -50,7 +40,7 @@ Base.:(==)(A::Alphabet, B::Alphabet) = Base.hash(A::Alphabet{T}, h::UInt) where T = hash(letters(A), hash(A.inversions, hash(h, hash(Alphabet{T})))) -Base.show(io::IO, A::Alphabet{T}) where T = print(io, Alphabet{T}, letters(A)) +Base.show(io::IO, A::Alphabet) = print(io, Alphabet, " ", letters(A)) hasinverse(i::Integer, A::Alphabet) = A.inversions[i] > 0 hasinverse(l::T, A::Alphabet{T}) where T = hasinverse(A[l], A) @@ -60,12 +50,12 @@ function Base.show(io::IO, ::MIME"text/plain", A::Alphabet{T}) where T print(io, "Empty alphabet of $(T)") else print(io, "Alphabet of $(T):") - for (i, l) in enumerate(letters(A)) + for (i, l) in pairs(letters(A)) print(io, "\n\t$(i).\t") show(io, l) if hasinverse(i, A) print(io, " = (") - show(io, letters(A)[A.inversions[i]]) + show(io, A[-i]) print(io, ")⁻¹") end end @@ -73,9 +63,9 @@ function Base.show(io::IO, ::MIME"text/plain", A::Alphabet{T}) where T end """ - function Base.push!(A::Alphabet{T}, symbols::Vararg{T,1}) where T + push!(A::Alphabet{T}, symbols::T...) where T -Pushes one or more elements of type `T` at the end of the alphabet `A`. +Push one or more elements of type `T` at the end of the alphabet `A`. # Example ```julia-repl @@ -88,7 +78,7 @@ Alphabet of String: 2. "b" ``` """ -function Base.push!(A::Alphabet{T}, symbols::Vararg{T,1}) where T +function Base.push!(A::Alphabet{T}, symbols::T...) where T for s in symbols if findfirst(symbol -> symbol == s, letters(A)) !== nothing error("Symbol $(s) already in the alphabet.") @@ -100,16 +90,13 @@ function Base.push!(A::Alphabet{T}, symbols::Vararg{T,1}) where T end """ - function set_inversion!(A::Alphabet{T}, x::T, y::T) where T + set_inversion!(A::Alphabet{T}, x::T, y::T) where T -Sets the inversion of `x` to `y` (and vice versa). +Set the inversion of `x` to `y` (and vice versa). # Example ```julia-repl -julia> A = Alphabet{String}() -Empty alphabet of String - -julia> push!(A, "a", "b", "c") +julia> A = Alphabet(["a", "b", "c"]) Alphabet of String: 1. "a" 2. "b" @@ -149,66 +136,37 @@ function set_inversion!(A::Alphabet{T}, x::T, y::T) where T end """ - function getindexbysymbol(A::Alphabet{T}, x::T) where T + getindex(A::Alphabet{T}, x::T) where T -Returns the position of the symbol `x` in the alphabet `A`. +Return the position of the symbol `x` in the alphabet `A`. # Example ```julia-repl -julia> A = Alphabet{String}() -Empty alphabet of String - -julia> push!(A, "a", "b", "c") +julia> A = Alphabet(["a", "b", "c"]) Alphabet of String: 1. "a" 2. "b" 3. "c" -julia> getindexbysymbol(A, "c") +julia> A["c"] 3 ``` """ -function getindexbysymbol(A::Alphabet{T}, x::T) where T +function Base.getindex(A::Alphabet{T}, x::T) where T if (index = findfirst(symbol -> symbol == x, letters(A))) === nothing throw(DomainError("Element '$(x)' not found in the alphabet")) end - index + return index end """ - function Base.getindex(A::Alphabet{T}, x::T) where T + getindex(A::Alphabet{T}, p::Integer) where T -Returns the position of the symbol `x` in the alphabet `A`. If you, by any chance, work with the alphabet of integers, use `getindexbysymbol`. +Return the symbol that holds the `p`th position in the alphabet `A`. If `p < 0`, then the inversion of the `|p|`th symbol is returned. # Example ```julia-repl -julia> A = Alphabet{String}() -Empty alphabet of String - -julia> push!(A, "a", "b", "c") -Alphabet of String: - 1. "a" - 2. "b" - 3. "c" - -julia> A["c"] -3 -``` -""" -Base.getindex(A::Alphabet{T}, x::T) where T = getindexbysymbol(A, x) - - -""" - function Base.getindex(A::Alphabet{T}, p::Integer) where T - -Returns the symbol that holds the `p`th position in the alphabet `A`. If `p < 0`, then it returns the inversion of the `|p|`th symbol. - -# Example -```julia-repl -julia> A = Alphabet{String}() -Empty alphabet of String - -julia> push!(A, "a", "b", "c") +julia> A = Alphabet(["a", "b", "c"]) Alphabet of String: 1. "a" 2. "b" @@ -220,20 +178,30 @@ Alphabet of String: 2. "b" 3. "c" = ("a")⁻¹ +julia> A["a"] +1 + julia> A[-A["a"]] "c" ``` """ -function Base.getindex(A::Alphabet{T}, p::Integer) where T +Base.@propagate_inbounds function Base.getindex(A::Alphabet, p::Integer) + @boundscheck checkbounds(letters(A), abs(p)) if p > 0 - return letters(A)[p] - elseif p < 0 && A.inversions[-p] > 0 - return letters(A)[A.inversions[-p]] + return @inbounds letters(A)[p] + elseif p < 0 && hasinverse(-p, A) + return @inbounds letters(A)[A.inversions[-p]] end throw(DomainError("Inversion of $(letters(A)[-p]) is not defined")) end +function Base.inv(A::Alphabet, i::Integer) + hasinverse(i, A) && return A.inversions[i] + + throw(DomainError(A[i], "is not invertible over $A")) +end + """ inv(A::Alphabet, w::AbstractWord) Return the inverse of a word `w` in the context of alphabet `A`. @@ -242,11 +210,11 @@ function Base.inv(A::Alphabet, w::AbstractWord) res = similar(w) n = length(w) for (i,l) in enumerate(Iterators.reverse(w)) - hasinverse(l, A) || throw(DomainError(w, "is not invertible over $A")) - res[i] = A.inversions[l] + res[i] = inv(A, l) end return res end + Base.inv(A::Alphabet{T}, a::T) where {T} = A[-A[a]] string_repr(w::AbstractWord, A::Alphabet) = diff --git a/test/alphabets.jl b/test/alphabets.jl index 0bde3b8b..36536e6a 100644 --- a/test/alphabets.jl +++ b/test/alphabets.jl @@ -1,11 +1,9 @@ @testset "Alphabets" begin - import KnuthBendix.getindexbysymbol, KnuthBendix.set_inversion! + import KnuthBendix.set_inversion! - @test Alphabet() isa Alphabet{Char} - @test_throws ErrorException Alphabet{Int}() - @test_throws ErrorException Alphabet([1, 2, 3]) - @test Alphabet([1, 2, 3], safe = false) isa Alphabet - @test Alphabet{Integer}(safe = false) isa Alphabet{Integer} + @test Alphabet{Char}() isa Alphabet{Char} + @test_throws AssertionError Alphabet{Int}() + @test_throws AssertionError Alphabet([1, 2, 3]) A = Alphabet{Char}() @test length(A.letters) == 0 && length(A.inversions) == 0 @@ -17,7 +15,7 @@ @test length(B.letters) == 3 && length(B.inversions) == 3 @test findfirst(i -> i != 0, B.inversions) === nothing - @test_throws ErrorException Alphabet(['a', 'b', 'a']) + @test_throws AssertionError Alphabet(['a', 'b', 'a']) push!(A, 'a', 'b', 'c') @test length(A.letters) == 3 && length(A.inversions) == 3 @@ -26,12 +24,10 @@ @test A[1] == 'a' && A[2] == 'b' && A[3] == 'c' @test A['a'] == 1 && A['b'] == 2 && A['c'] == 3 - @test getindexbysymbol(A, 'b') == 2 @test B[1] == 'a' && B[2] == 'b' && B[3] == 'c' @test B['a'] == 1 && B['b'] == 2 && B['c'] == 3 - @test getindexbysymbol(B, 'c') == 3 - @test_throws DomainError getindexbysymbol(B, 'd') + @test_throws DomainError B['d'] @test_throws ErrorException set_inversion!(A, 'd', 'e') @test_throws ErrorException set_inversion!(A, 'a', 'e') diff --git a/test/rewriting.jl b/test/rewriting.jl index f7b4ecb3..e9b5f5b3 100644 --- a/test/rewriting.jl +++ b/test/rewriting.jl @@ -2,7 +2,7 @@ import KnuthBendix.set_inversion! - A = Alphabet{String}(["a", "e", "b", "p"]) + A = Alphabet(["a", "e", "b", "p"]) set_inversion!(A, "a", "e") set_inversion!(A, "b", "p") lenlexord = LenLex(A) From 1d6c3f681b2670c0137aa482ef717cf877373ec2 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Sun, 23 May 2021 23:24:15 +0200 Subject: [PATCH 4/7] update tests * operations on rws systems * alphabets --- test/alphabets.jl | 94 +++++++++++++---------- test/rewriting.jl | 191 +++++++++++++++++++++++++--------------------- 2 files changed, 157 insertions(+), 128 deletions(-) diff --git a/test/alphabets.jl b/test/alphabets.jl index 36536e6a..f7569859 100644 --- a/test/alphabets.jl +++ b/test/alphabets.jl @@ -4,47 +4,59 @@ @test Alphabet{Char}() isa Alphabet{Char} @test_throws AssertionError Alphabet{Int}() @test_throws AssertionError Alphabet([1, 2, 3]) - - A = Alphabet{Char}() - @test length(A.letters) == 0 && length(A.inversions) == 0 - @test sprint(show, A) isa String - - B = Alphabet(['a', 'b', 'c']) - @test sprint(show, B) isa String - @test B isa Alphabet{Char} - @test length(B.letters) == 3 && length(B.inversions) == 3 - @test findfirst(i -> i != 0, B.inversions) === nothing - @test_throws AssertionError Alphabet(['a', 'b', 'a']) - push!(A, 'a', 'b', 'c') - @test length(A.letters) == 3 && length(A.inversions) == 3 - @test findfirst(i -> i != 0, A.inversions) === nothing - @test_throws ErrorException push!(A, 'a') - - @test A[1] == 'a' && A[2] == 'b' && A[3] == 'c' - @test A['a'] == 1 && A['b'] == 2 && A['c'] == 3 - - @test B[1] == 'a' && B[2] == 'b' && B[3] == 'c' - @test B['a'] == 1 && B['b'] == 2 && B['c'] == 3 - @test_throws DomainError B['d'] - - @test_throws ErrorException set_inversion!(A, 'd', 'e') - @test_throws ErrorException set_inversion!(A, 'a', 'e') - - set_inversion!(A, 'a', 'b') - @test A[-2] == 'a' && A[-1] == 'b' - set_inversion!(A, 'b', 'c') - @test A[-2] == 'c' && A[-3] == 'b' - set_inversion!(A, 'a', 'c') - @test A[-1] == 'c' && A[-3] == 'a' - @test_throws DomainError A[-2] - - @test A[A[1]] == 1 - - w = KnuthBendix.Word([1, 2]) - @test_throws DomainError inv(A, w) # b is not invertible - w = KnuthBendix.Word([1, 1, 3]) - @test inv(A, w) == Word([1, 3, 3]) # inv(a*a*c) - @test inv(A, inv(A, w)) == w + let B = Alphabet(['a', 'b', 'c']) + @test B isa Alphabet{Char} + @test sprint(show, B) isa String + @test sprint(show, MIME"text/plain"(), B) isa String + @test length(B.letters) == 3 && length(B.inversions) == 3 + @test findfirst(i -> i != 0, B.inversions) === nothing + + @test B[1] == 'a' && B[2] == 'b' && B[3] == 'c' + @test B['a'] == 1 && B['b'] == 2 && B['c'] == 3 + @test_throws DomainError B['d'] + end + + let A = Alphabet{Char}() + @test sprint(show, A) isa String + @test sprint(show, MIME"text/plain"(), A) isa String + + @test length(A.letters) == 0 && length(A.inversions) == 0 + + push!(A, 'a', 'b', 'c') + @test length(A.letters) == 3 && length(A.inversions) == 3 + @test findfirst(i -> i != 0, A.inversions) === nothing + @test_throws ErrorException push!(A, 'a') + + @test A[1] == 'a' && A[2] == 'b' && A[3] == 'c' + @test A['a'] == 1 && A['b'] == 2 && A['c'] == 3 + + @test_throws ErrorException set_inversion!(A, 'd', 'e') + @test_throws ErrorException set_inversion!(A, 'a', 'e') + + set_inversion!(A, 'a', 'b') + @test A[-2] == 'a' && A[-1] == 'b' + set_inversion!(A, 'b', 'c') + @test A[-2] == 'c' && A[-3] == 'b' + set_inversion!(A, 'a', 'c') + @test A[-1] == 'c' && A[-3] == 'a' + @test_throws DomainError A[-2] + + @test sprint(show, A) isa String + @test sprint(show, MIME"text/plain"(), A) isa String + + @test A[A[1]] == 1 + end + + @testset "Inverting using Alphabet" begin + A = Alphabet(['a', 'b', 'A']) + KnuthBendix.set_inversion!(A, 'a', 'A') + + w = KnuthBendix.Word([1, 2]) + @test_throws DomainError inv(A, w) # b is not invertible + w = KnuthBendix.Word([1, 1, 3]) + @test inv(A, w) == Word([1, 3, 3]) # inv(a*a*c) + @test inv(A, inv(A, w)) == w + end end diff --git a/test/rewriting.jl b/test/rewriting.jl index e9b5f5b3..bb04cb6c 100644 --- a/test/rewriting.jl +++ b/test/rewriting.jl @@ -1,93 +1,110 @@ @testset "Rewriting" begin - import KnuthBendix.set_inversion! + Al = Alphabet(["a", "e", "b", "p"]) + KnuthBendix.set_inversion!(Al, "a", "e") + KnuthBendix.set_inversion!(Al, "b", "p") + lenlexord = LenLex(Al) - A = Alphabet(["a", "e", "b", "p"]) - set_inversion!(A, "a", "e") - set_inversion!(A, "b", "p") - lenlexord = LenLex(A) - - a = Word([1,2]) - b = Word([2,1]) - c = Word([3,4]) - d = Word([4,3]) + a, A, b, B = [Word([i]) for i in 1:4] ε = one(a) - ba = Word([3,1]) - ab = Word([1,3]) - - s = RewritingSystem([a=>ε, b=>ε, c=>ε, d=>ε, ba=>ab], lenlexord, bare=true) - z = empty(s) - - @test s isa KnuthBendix.AbstractRewritingSystem - @test s isa RewritingSystem - - @test s !== z - @test isempty(z) - @test !isempty(s) - - push!(z, c=>ε) - @test KnuthBendix.rules(z) == [c=>ε] - @test KnuthBendix.ordering(z) == lenlexord - - pushfirst!(z, b=>ε) - @test KnuthBendix.rules(z)[1] == (b=>ε) - @test KnuthBendix.rules(z) == [b=>ε, c=>ε] - - append!(z, RewritingSystem([ba=>ab], lenlexord; bare=true)) - @test KnuthBendix.rules(z) == [b=>ε, c=>ε, ba=>ab] - prepend!(z, RewritingSystem([a=>ε], lenlexord; bare=true)) - @test KnuthBendix.rules(z) == [a=>ε, b=>ε, c=>ε, ba=>ab] - - @test KnuthBendix.rules(z)[1] == (a=>ε) - @test length(z) == 4 - @test length(KnuthBendix.active(z)) == length(z) - - KnuthBendix.setinactive!(z, 4) - @test !KnuthBendix.isactive(z, 4) - @test KnuthBendix.rules(z)[KnuthBendix.active(z)] == [a=>ε, b=>ε, c=>ε] - @test KnuthBendix.active(z) == [true, true, true, false] - - KnuthBendix.setactive!(z, 4) - @test KnuthBendix.isactive(z, 4) - - @test KnuthBendix.rules(z) == [a=>ε, b=>ε, c=>ε, ba=>ab] - - insert!(z, 4, d=>ε) == s - @test issubset(KnuthBendix.rules(z), KnuthBendix.rules(s)) - deleteat!(z, 5) - @test KnuthBendix.rules(z) == [a=>ε, b=>ε, c=>ε, d=>ε] - deleteat!(z, 3:4) == RewritingSystem([a=>ε, b=>ε], lenlexord, bare=true) - @test KnuthBendix.rules(z) == [a=>ε, b=>ε] - - @test KnuthBendix.rewrite_from_left(a, s) == ε - @test KnuthBendix.rewrite_from_left(c, z) == c - - KnuthBendix.setinactive!(s, 1) - @test KnuthBendix.rewrite_from_left(a, s) == a - KnuthBendix.setactive!(s, 1) - @test KnuthBendix.rewrite_from_left(a, s) == ε - - @test pop!(z) == (b=>ε) - @test popfirst!(z) == (a=>ε) - @test length(KnuthBendix.active(z)) == 0 - - push!(z, c=>ε) - @test KnuthBendix.rules(empty!(z)) == KnuthBendix.rules(empty(s)) - @test KnuthBendix.rewrite_from_left(c, z) == c - - push!(A, "z") - - prefix = Word(rand(1:length(A)-1, 100)) # all invertible - suffix = Word(rand(1:length(A)-1, 100)) # all invertible - l = Word([5,1,2,2]) - r = Word([5,1,2,1]) - - @test KnuthBendix.simplifyrule!(prefix*l, prefix*r, A) == (l, r) - @test KnuthBendix.simplifyrule!(l*suffix, r*suffix, A) == (l, r) - @test KnuthBendix.simplifyrule!(prefix*l*suffix, prefix*r*suffix, A) == (l, r) - @test KnuthBendix.simplifyrule!(l*suffix, prefix*r*Word([5]), A) == (l*suffix, prefix*r*Word([5])) - @test KnuthBendix.simplifyrule!(copy(l), copy(r), A) == (l, r) - - @test sprint(show, z) isa String + @testset "Free Rewriting" begin + @test KnuthBendix.rewrite_from_left(a * A, Al) == ε + @test KnuthBendix.rewrite_from_left(a * A * b, Al) == b + @test KnuthBendix.rewrite_from_left(a * B * b, Al) == a + @test KnuthBendix.rewrite_from_left(a * B * b * A, Al) == ε + @test KnuthBendix.rewrite_from_left(a * B * b * a, Al) == a * a + @test KnuthBendix.rewrite_from_left(a * B * b * a * A, Al) == a + @test KnuthBendix.rewrite_from_left(a * b * A * B, Al) == a * b * A * B + end + + @testset "Rule simplification" begin + Al = deepcopy(Al) + push!(Al, "z") + + prefix = Word(rand(1:length(Al)-1, 100)) # all invertible + suffix = Word(rand(1:length(Al)-1, 100)) # all invertible + l = Word([5, 1, 2, 2]) + r = Word([5, 1, 2, 1]) + + @test KnuthBendix.simplifyrule!(prefix * l, prefix * r, Al) == (l, r) + @test KnuthBendix.simplifyrule!(l * suffix, r * suffix, Al) == (l, r) + @test KnuthBendix.simplifyrule!(prefix * l * suffix, prefix * r * suffix, Al) == + (l, r) + @test KnuthBendix.simplifyrule!(l * suffix, prefix * r * Word([5]), Al) == + (l * suffix, prefix * r * Word([5])) + @test KnuthBendix.simplifyrule!(copy(l), copy(r), Al) == (l, r) + end + + @testset "Rewriting System operations" begin + + R = RewritingSystem( + [a * A => ε, A * a => ε, b * B => ε, B * b => ε, a * b => b * a], + lenlexord, + bare = true, + ) + + Z = empty(R) + + @test R isa KnuthBendix.AbstractRewritingSystem + @test R isa RewritingSystem + + @test R !== Z + @test isempty(Z) + @test !isempty(R) + + push!(Z, b * B => ε) + @test KnuthBendix.rules(Z) == [b * B => ε] + @test KnuthBendix.ordering(Z) == lenlexord + + pushfirst!(Z, A * a => ε) + @test KnuthBendix.rules(Z)[1] == (A * a => ε) + @test KnuthBendix.rules(Z) == [A * a => ε, b * B => ε] + + append!(Z, RewritingSystem([a * b => b * a], lenlexord; bare = true)) + @test KnuthBendix.rules(Z) == [A * a => ε, b * B => ε, a * b => b * a] + prepend!(Z, RewritingSystem([a * A => ε], lenlexord; bare = true)) + @test KnuthBendix.rules(Z) == [a * A => ε, A * a => ε, b * B => ε, a * b => b * a] + + @test KnuthBendix.rules(Z)[1] == (a * A => ε) + @test length(Z) == 4 + @test length(KnuthBendix.active(Z)) == length(Z) + + KnuthBendix.setinactive!(Z, 4) + @test !KnuthBendix.isactive(Z, 4) + @test KnuthBendix.rules(Z)[KnuthBendix.active(Z)] == + [a * A => ε, A * a => ε, b * B => ε] + @test KnuthBendix.active(Z) == [true, true, true, false] + + KnuthBendix.setactive!(Z, 4) + @test KnuthBendix.isactive(Z, 4) + + @test KnuthBendix.rules(Z) == [a * A => ε, A * a => ε, b * B => ε, a * b => b * a] + + insert!(Z, 4, B * b => ε) == R + @test issubset(KnuthBendix.rules(Z), KnuthBendix.rules(R)) + deleteat!(Z, 5) + @test KnuthBendix.rules(Z) == [a * A => ε, A * a => ε, b * B => ε, B * b => ε] + deleteat!(Z, 3:4) == + RewritingSystem([a * A => ε, A * a => ε], lenlexord, bare = true) + @test KnuthBendix.rules(Z) == [a * A => ε, A * a => ε] + + @test KnuthBendix.rewrite_from_left(a * A, R) == ε + @test KnuthBendix.rewrite_from_left(b * B, Z) == b * B + + KnuthBendix.setinactive!(R, 1) + @test KnuthBendix.rewrite_from_left(a * A, R) == a * A + KnuthBendix.setactive!(R, 1) + @test KnuthBendix.rewrite_from_left(a * A, R) == ε + + @test pop!(Z) == (A * a => ε) + @test popfirst!(Z) == (a * A => ε) + @test length(KnuthBendix.active(Z)) == 0 + + push!(Z, b * B => ε) + @test KnuthBendix.rules(empty!(Z)) == KnuthBendix.rules(empty(R)) + @test KnuthBendix.rewrite_from_left(b * B, Z) == b * B + + @test sprint(show, Z) isa String + end end From 4730f1585b33aa4be7c29294d712c2166f930ae6 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Sun, 23 May 2021 23:42:22 +0200 Subject: [PATCH 5/7] bump to 0.2.0 --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 211f5bbe..72f9cf29 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,14 @@ name = "KnuthBendix" uuid = "c2604015-7b3d-4a30-8a26-9074551ec60a" authors = ["Marek Kaluba ", "Mikołaj Pabiszczak "] -version = "0.1.1" +version = "0.2.0" [deps] MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" [compat] -julia = "1" MacroTools = "0.5" +julia = "1" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From c8fa6cd77e6411d2c27331262bd88932e0ae51b3 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Tue, 25 May 2021 11:59:17 +0200 Subject: [PATCH 6/7] =?UTF-8?q?string=5Frepr=20=E2=86=92=20print=5Frepr=20?= =?UTF-8?q?+=20pretty=20printing=20of=20syllables?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/alphabets.jl | 41 ++++++++++++++++++++++++++++++++++++++--- src/automata.jl | 12 +++++++++--- src/orderings.jl | 3 --- src/rewriting.jl | 8 +++++--- test/alphabets.jl | 22 ++++++++++++++++++++++ 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/alphabets.jl b/src/alphabets.jl index df11e10d..fa721cef 100644 --- a/src/alphabets.jl +++ b/src/alphabets.jl @@ -209,7 +209,7 @@ Return the inverse of a word `w` in the context of alphabet `A`. function Base.inv(A::Alphabet, w::AbstractWord) res = similar(w) n = length(w) - for (i,l) in enumerate(Iterators.reverse(w)) + for (i, l) in zip(eachindex(res), Iterators.reverse(w)) res[i] = inv(A, l) end return res @@ -217,5 +217,40 @@ end Base.inv(A::Alphabet{T}, a::T) where {T} = A[-A[a]] -string_repr(w::AbstractWord, A::Alphabet) = - (isone(w) ? "(empty word)" : join((A[i] for i in w), "*")) +function _print_syllable(io, symbol, pow) + str = string(symbol) + if length(str) > 3 && endswith(str, "^-1") + print(io, str[1:end-3], "^-", pow) + else + if pow == 1 + print(io, str) + else + print(io, str, "^", pow) + end + end +end + +function print_repr(io::IO, w::AbstractWord, A::Alphabet, sep="*") + if isone(w) + print(io, "(empty word)") + else + first_syllable = true + idx = 1 + pow = 1 + while idx < length(w) + if w[idx] == w[idx+1] + pow += 1 + idx += 1 + else + first_syllable || print(io, sep) + _print_syllable(io, A[w[idx]], pow) + first_syllable = false + pow = 1 + idx += 1 + end + end + @assert idx == length(w) + first_syllable || print(io, sep) + _print_syllable(io, A[w[idx]], pow) + end +end diff --git a/src/automata.jl b/src/automata.jl index 201d0dde..f1cba8e9 100644 --- a/src/automata.jl +++ b/src/automata.jl @@ -254,11 +254,17 @@ end function Base.show(io::IO, a::AbstractAutomaton) println(io, "Automaton with $(length(states(a))) states") for (i, state) in enumerate(states(a)) - println(io, " $i. Edges leaving state (", string_repr(name(state), alphabet(a)), "):") + print(io, " $i. Edges leaving state (") + print_repr(io, name(state), alphabet(a)) + println(io, "):") for (i, tostate) in enumerate(outedges(state)) - !isfailstate(tostate) && println(io, - " ", " - with label ", alphabet(a)[i], " to state (", string_repr(name(tostate), alphabet(a)), ")") + if !isfailstate(tostate) + print(io, + " ", " - with label ", alphabet(a)[i], " to state (") + print_repr(io, name(tostate), alphabet(a)) + println(io, ")") + end end end end diff --git a/src/orderings.jl b/src/orderings.jl index ed498682..ecf5b9da 100644 --- a/src/orderings.jl +++ b/src/orderings.jl @@ -12,11 +12,8 @@ defined). """ abstract type WordOrdering <: Ordering end - alphabet(o::WordOrdering) = o.A Base.:(==)(o1::T, o2::T) where {T<:WordOrdering} = alphabet(o1) == alphabet(o2) -string_repr(W::AbstractWord, o::WordOrdering) = string_repr(W, alphabet(o)) - """ struct LenLex{T} <: WordOrdering diff --git a/src/rewriting.jl b/src/rewriting.jl index 76f1d41b..281d30d6 100644 --- a/src/rewriting.jl +++ b/src/rewriting.jl @@ -257,9 +257,11 @@ end function Base.show(io::IO, rws::RewritingSystem) println(io, "Rewriting System with $(length(rules(rws))) rules ordered by $(ordering(rws)):") for (i, (lhs, rhs)) in enumerate(rules(rws)) - lhs_str = string_repr(lhs, ordering(rws)) - rhs_str = string_repr(rhs, ordering(rws)) act = isactive(rws, i) ? "✓" : " " - println(io, lpad("$i", 4, " "), " $act ", lhs_str, "\t → \t", rhs_str) + print(io, lpad("$i", 4, " "), " $act ") + print_repr(io, lhs, alphabet(rws)) + print(io, "\t → \t") + print_repr(io, rhs, alphabet(rws)) + println(io, "") end end diff --git a/test/alphabets.jl b/test/alphabets.jl index f7569859..2d2fbfd6 100644 --- a/test/alphabets.jl +++ b/test/alphabets.jl @@ -58,5 +58,27 @@ w = KnuthBendix.Word([1, 1, 3]) @test inv(A, w) == Word([1, 3, 3]) # inv(a*a*c) @test inv(A, inv(A, w)) == w + + @test sprint(KnuthBendix.print_repr, w, A) == "a^2*A" + @test sprint(KnuthBendix.print_repr, inv(A, w), A) == "a*A^2" + + B = Alphabet(["a", "a^-1", "c"], [2,1,0]) + w = KnuthBendix.Word([1]) + @test sprint(KnuthBendix.print_repr, w, B) == "a" + w = KnuthBendix.Word([1, 1]) + @test sprint(KnuthBendix.print_repr, w, B) == "a^2" + w = KnuthBendix.Word([2]) + @test sprint(KnuthBendix.print_repr, w, B) == "a^-1" + w = KnuthBendix.Word([2,2]) + @test sprint(KnuthBendix.print_repr, w, B) == "a^-2" + + w = KnuthBendix.Word([3, 1]) + @test sprint(KnuthBendix.print_repr, w, B) == "c*a" + w = KnuthBendix.Word([3, 1, 1]) + @test sprint(KnuthBendix.print_repr, w, B) == "c*a^2" + w = KnuthBendix.Word([3, 2]) + @test sprint(KnuthBendix.print_repr, w, B) == "c*a^-1" + w = KnuthBendix.Word([3, 2,2]) + @test sprint(KnuthBendix.print_repr, w, B) == "c*a^-2" end end From 82efebae0bdc0a5d6b1f2bed82120fddf09e2612 Mon Sep 17 00:00:00 2001 From: Marek Kaluba Date: Wed, 26 May 2021 10:58:19 +0200 Subject: [PATCH 7/7] factor out @warn when maxrules are exceeded --- src/kbs.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/kbs.jl b/src/kbs.jl index f81d042d..198d570e 100644 --- a/src/kbs.jl +++ b/src/kbs.jl @@ -2,6 +2,10 @@ # Crude, i.e., KBS1 implementation ################################## +_kb_maxrules_warning(maxrules) = + @warn("Maximum number of rules ($maxrules) reached. The rewriting system may not be confluent. + You may retry `knuthbendix` with a larger `maxrules` kwarg.") + """ knuthbendix1(rws::RewritingSystem[, o::Ordering=ordering(rs); maxrules=100]) Implements a Knuth-Bendix algorithm that yields reduced, confluent rewriting @@ -17,8 +21,7 @@ function knuthbendix1!(rws::RewritingSystem, o::Ordering = ordering(rws); maxrul while i ≤ length(ss) @debug "at iteration $i rws contains $(length(ss.rwrules)) rules" if length(ss) >= maxrules - @warn("Maximum number of rules ($maxrules) in the RewritingSystem reached. - You may retry with `maxrules` kwarg set to higher value.") + _kb_maxrules_warning(maxrules) break end for j in 1:i @@ -64,8 +67,7 @@ function knuthbendix2!(rws::RewritingSystem, while i ≤ length(rules(rws)) # @debug "number_of_active_rules" sum(active(rws)) if sum(active(rws)) > maxrules - @warn("Maximum number of rules ($maxrules) in the RewritingSystem reached. - You may retry with `maxrules` kwarg set to higher value.") + _kb_maxrules_warning(maxrules) break end j = 1 @@ -116,8 +118,7 @@ function knuthbendix2deleteinactive!(rws::RewritingSystem{W}, while get_i(work) ≤ length(rules(rws)) # @debug "number_of_active_rules" sum(active(rws)) if sum(active(rws)) > maxrules - @warn("Maximum number of rules ($maxrules) in the RewritingSystem reached. - You may retry with `maxrules` kwarg set to higher value.") + _kb_maxrules_warning(maxrules) break end work.j = 1 @@ -163,8 +164,7 @@ function knuthbendix2automaton!(rws::RewritingSystem{W}, while get_i(work) ≤ length(rws) # @debug "number_of_active_rules" sum(active(rws)) if sum(active(rws)) > maxrules - @warn("Maximum number of rules ($maxrules) in the RewritingSystem reached. - You may retry with `maxrules` kwarg set to higher value.") + _kb_maxrules_warning(maxrules) break end work.j = 1