Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Js/nc groebner optimization stable #1292

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
24cb8c2
add some Groebner machinery for FreeAssociativeAlgebra
tthsqe12 May 5, 2022
d80fd56
added an isless function for FreeAssAlgElems and changed the terminat…
julien-schanz May 11, 2022
ebf71e3
replaced the divides_leftmost substring check with a kmp variant for …
julien-schanz May 26, 2022
2e851e1
added an implementation of aho corasick
julien-schanz Jun 9, 2022
8210424
started implementing normal form using aho corasick
julien-schanz Jun 10, 2022
f337310
implemented a normal form computation using aho corasick
julien-schanz Jun 10, 2022
d8fa4d8
implemented normal form using the more efficient substring matching a…
julien-schanz Jun 13, 2022
adfad26
added the previous version for benchmarking
julien-schanz Jun 13, 2022
910674b
added manifest
julien-schanz Jun 13, 2022
8de2e0a
faster gb computation works now
julien-schanz Jun 15, 2022
46c8f38
modified interreduce and made ahocorasick automata mutable
julien-schanz Jun 22, 2022
e51bfa7
started adapting the gebauer möller criteria to the new types
julien-schanz Jun 24, 2022
c665b34
adapted remove redundancies to the new types and corrected has_overlap
julien-schanz Jul 5, 2022
a53d92e
cleaned the code a bit
julien-schanz Jul 18, 2022
3a5cfde
removed more print statements
julien-schanz Jul 18, 2022
022819c
added some tests, cleaned code and added a hash function for
julien-schanz Jul 19, 2022
0933b0f
fixed merge
julien-schanz Jul 19, 2022
135c935
removed unnecessary dependencies and comments
julien-schanz Oct 20, 2022
0f7fe94
added docstrings to all exported functions in AhoCorasick.jl and Free…
julien-schanz Mar 10, 2023
1549e89
fixed merge conflict in AbstractAlgebra.jl
julien-schanz Mar 10, 2023
20db65b
fixed merge conflict
julien-schanz Mar 13, 2023
51da2f6
integrated changes from the big renaming
julien-schanz Mar 14, 2023
21da71b
merged docstrings into this branch
julien-schanz Mar 14, 2023
24adfb8
added documentation of the groebner basis function and aho corasick a…
julien-schanz Mar 17, 2023
9c6d94c
fixed typo in the documentation
julien-schanz Mar 17, 2023
ff19858
Update free_associative_algebra.md
julien-schanz Mar 17, 2023
fb393b2
Update free_associative_algebra.md
julien-schanz Mar 17, 2023
2abd28f
removed tracking from Manifest.toml
julien-schanz Mar 21, 2023
3586f2f
Merge branch 'js/nc_groebner_optimization_stable' of https://github.c…
julien-schanz Mar 21, 2023
e41b0b9
added a test case for normal_form_weak
julien-schanz Mar 21, 2023
d6cb270
fixed imports in AhoCorasick.jl
julien-schanz Mar 21, 2023
6602fc7
Update free_associative_algebra.md
julien-schanz Mar 21, 2023
6f9afbb
incorporated remarks by Max Horn
julien-schanz Mar 22, 2023
d1e4787
Run OscarCI teests on master, too (#1284)
fingolfin Mar 14, 2023
575ea86
Update oscar.yml
thofma Mar 17, 2023
88c6df6
Fix bug in universal polynomial ring (#1286)
thofma Mar 17, 2023
2106c6c
Update Project.toml
thofma Mar 17, 2023
b372561
Update Project.toml
thofma Mar 17, 2023
17eb859
jldoctest: add missing end markers
benlorenz Mar 18, 2023
8c3fcc5
Allow / as shortcut for divexact
fingolfin Mar 16, 2023
804c4bf
Move `polynomial_ring` code to abstract level (#1282)
mgkurtz Mar 20, 2023
d04a58e
add `ngens` for `FreeAssAlgebra` (#1295)
lgoettgens Mar 24, 2023
b232a4e
OscarCI: fix running without PR_NUMBER, e.g. on master
fingolfin Mar 24, 2023
6bda614
Add is_finite/is_perfect fallbacks
thofma Mar 24, 2023
6fca238
make `get_attribute` unambiguous (#1298)
lgoettgens Mar 25, 2023
8582322
Introduce `VarName` (#1291)
mgkurtz Mar 25, 2023
9c75436
Add `*poly*_type` methods for elements (#1290)
mgkurtz Mar 25, 2023
12ee122
Clarify comments for new get_attribute methods
fingolfin Mar 26, 2023
4df7adf
Fix spurious test failure in pseudo_inv (#1299)
fingolfin Mar 26, 2023
f801367
Rename poly_ring_type -> dense_poly_ring_type (#1300)
fingolfin Mar 26, 2023
68842fa
Version 0.28.4
fingolfin Mar 26, 2023
70601eb
Set DEV version
fingolfin Mar 26, 2023
20f9b7b
Remove `get_field` and `set_field!` (#1265)
fingolfin Mar 27, 2023
dfe9371
Enhance *poly*_type methods (#1303)
fingolfin Mar 27, 2023
817d467
add some Groebner machinery for FreeAssociativeAlgebra
tthsqe12 May 5, 2022
a964c18
replaced the divides_leftmost substring check with a kmp variant for …
julien-schanz May 26, 2022
8e3cae3
cleaned the code a bit
julien-schanz Jul 18, 2022
e9d34e7
removed unnecessary dependencies and comments
julien-schanz Oct 20, 2022
d74b254
integrated changes from the big renaming
julien-schanz Mar 14, 2023
6739af9
added docstrings to all exported functions in AhoCorasick.jl and Free…
julien-schanz Mar 10, 2023
344f3c6
added documentation of the groebner basis function and aho corasick a…
julien-schanz Mar 17, 2023
df7c3e1
incorporated remarks by Max Horn
julien-schanz Mar 22, 2023
6393fc5
added the importing of priority queue enqueueing and dequeueing in Fr…
julien-schanz Mar 27, 2023
c0e094d
fixed the docstrings and the export/import statements
julien-schanz Mar 27, 2023
5e3880f
imported normal_form_weak in the test
julien-schanz Mar 28, 2023
9662933
merged AbstractAlgebra master
julien-schanz Mar 28, 2023
e595189
Merge branch 'master' of https://github.com/Nemocas/AbstractAlgebra.j…
julien-schanz Mar 28, 2023
10b9134
added test cases for the normal form without aho-corasick-automaton a…
julien-schanz Apr 3, 2023
6483879
changed all instances of for i = back into for i in and expanded on t…
julien-schanz Apr 6, 2023
4b2b049
changed @doc Markdown.doc to @doc or @doc raw in comments
julien-schanz May 15, 2023
0482a64
Merge branch 'js/nc_groebner_optimization_stable' of https://github.c…
julien-schanz May 15, 2023
4630e05
merged the latest AbstractAlgebra master branch and fixed a test case…
julien-schanz May 22, 2023
3e8d387
merged the latest abstract algebra main branch
julien-schanz Jun 14, 2023
f5f165d
merged latest abstract algebra branch
julien-schanz Jun 22, 2023
b00add0
fixed merge conflict of one empty line in FreeAssAlgebra.jl
julien-schanz Jun 28, 2023
8ec6ef3
Merge branch 'master' of https://github.com/Nemocas/AbstractAlgebra.j…
julien-schanz Jun 28, 2023
f06043a
removed the braces around type variable instantiation
julien-schanz Jun 29, 2023
955f980
Merge branch 'js/nc_groebner_optimization_stable' of https://github.c…
julien-schanz Jun 29, 2023
8f90c18
reformatted FreeAssAlgebra.jl for consistent whitespace
julien-schanz Jun 29, 2023
e5d32be
added a compat entry for DataStructures
julien-schanz Jun 29, 2023
9a29b09
fixed a doctest in free_associative_algebra.md and added a simple tes…
julien-schanz Jul 3, 2023
1bb6853
added access functions to the entries of AhoCorasickMatch and adapted…
julien-schanz Jul 4, 2023
a3dd379
worked in the remarks for AhoCorasick.jl
julien-schanz Jul 6, 2023
29d7b37
worked in the remarks for FreeAssAlgebraGroebner.jl, in particular ch…
julien-schanz Jul 6, 2023
56c1305
added some test cases for overlap and obstruction functions in FreeAs…
julien-schanz Jul 6, 2023
e433bd3
removed a semicolon from the doctest
julien-schanz Jul 18, 2023
eb666e9
merged the AbstractAlgebra master into this branch and resolved confl…
julien-schanz Jul 18, 2023
64a8871
exported the new access functions for AhoCorasickMatch and fixed the …
julien-schanz Jul 18, 2023
5e55c87
fixed the aho corasick doctest again
julien-schanz Jul 24, 2023
9cce960
fixed the failure of making docs by changing CurrentModule in free_as…
julien-schanz Jul 24, 2023
62185df
removed trailing newline in .gitignore and unnecessary braces around …
julien-schanz Jul 24, 2023
80578a9
worked in comments by Max Horn
julien-schanz Aug 25, 2023
8f223b5
removed the DataStructures dependency by adding the file PriorityQueu…
julien-schanz Aug 25, 2023
9449acb
fixed merge conflict
julien-schanz Aug 25, 2023
9a37fd1
export PriorityQueue added to PriorityQueue.jl
julien-schanz Aug 25, 2023
03527de
fixed PriorityQueue.jl doctests
julien-schanz Aug 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion docs/src/free_associative_algebra.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
```@meta
CurrentModule = AbstractAlgebra
CurrentModule = AbstractAlgebra.Generic
DocTestSetup = quote
using AbstractAlgebra
end
Expand Down Expand Up @@ -218,3 +218,35 @@ julia> collect(exponent_words(3*b*a*c - b + c + 2))
[]
```

### Groebner bases

The function `groebner_basis` provides the computation of a Groebner basis of an ideal, given a set of
generators of that ideal.
Since such a Groebner basis is not necessarily finite, one can additionally pass a `reduction_bound`
to the function, to only compute a partial Groebner basis.

**Examples**

```jldoctest; setup = :(using AbstractAlgebra)
julia> R, (x, y, u, v, t, s) = free_associative_algebra(GF(2), ["x", "y", "u", "v", "t", "s"])
fingolfin marked this conversation as resolved.
Show resolved Hide resolved
(Free associative algebra on 6 indeterminates over finite field F_2, AbstractAlgebra.Generic.FreeAssAlgElem{AbstractAlgebra.GFElem{Int64}}[x, y, u, v, t, s])

julia> g = Generic.groebner_basis([u*(x*y)^3 + u*(x*y)^2 + u + v, (y*x)^3*t + (y*x)^2*t + t + s])
5-element Vector{AbstractAlgebra.Generic.FreeAssAlgElem{AbstractAlgebra.GFElem{Int64}}}:
u*x*y*x*y*x*y + u*x*y*x*y + u + v
y*x*y*x*y*x*t + y*x*y*x*t + t + s
u*x*s + v*x*t
u*x*y*x*s + v*x*y*x*t
u*x*y*x*y*x*s + v*x*y*x*y*x*t
```

In order to check whether a given element of the algebra is in the ideal generated by a Groebner
basis `g`, one can compute its normal form.
```jldoctest; setup = :(using AbstractAlgebra)
julia> R, (x, y, u, v, t, s) = free_associative_algebra(GF(2), ["x", "y", "u", "v", "t", "s"]);

julia> g = Generic.groebner_basis([u*(x*y)^3 + u*(x*y)^2 + u + v, (y*x)^3*t + (y*x)^2*t + t + s]);

julia> normal_form(u*(x*y)^3*s*t + u*(x*y)^2*s*t +u*s*t + v*s*t, g)
0
```
2 changes: 2 additions & 0 deletions src/AbstractAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -644,12 +644,14 @@ import .Generic: finish
import .Generic: fit!
import .Generic: gcd
import .Generic: gcdx
import .Generic: groebner_basis
import .Generic: has_bottom_neighbor
import .Generic: has_left_neighbor
import .Generic: hash
import .Generic: hooklength
import .Generic: image_fn
import .Generic: image_map
import .Generic: interreduce!
import .Generic: intersection
import .Generic: inv!
import .Generic: inverse_fn
Expand Down
4 changes: 4 additions & 0 deletions src/Generic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ include("generic/MapCache.jl")

include("generic/Ideal.jl")

include("generic/AhoCorasick.jl")

include("generic/FreeAssAlgebraGroebner.jl")

###############################################################################
#
# Temporary miscellaneous files being moved from Hecke.jl
Expand Down
255 changes: 255 additions & 0 deletions src/generic/AhoCorasick.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
#
# FreeAssAhoCorasick.jl : implement bulk divide check for leading terms of free associative Algebra elements
# for use e.g. in Groebner Basis computation
#
###############################################################################

export aho_corasick_automaton

export AhoCorasickAutomaton

export AhoCorasickMatch

export insert_keyword!

export search

export last_position

export keyword_index

export keyword

#export Word

const Word = Vector{Int}

struct Queue{T}
data::Vector{T}
end

function Queue{T}() where T
return Queue{T}(T[])
end

function enqueue!(q::Queue{T}, val::T) where T
push!(q.data, val)
end
function dequeue!(q::Queue)
return popfirst!(q.data)
end
isempty(q::Queue) = isempty(q.data)

@doc """
AhoCorasickAutomaton

An Aho-Corasick automaton, which can be used to efficiently search for a fixed list of keywords (vectors of Ints) in
arbitrary lists of integers

# Examples
```jldoctest; setup = :(using AbstractAlgebra)
julia> keywords = [[1, 2, 3, 4], [1, 5, 4], [4, 1, 2], [1, 2]];

julia> aut = Generic.aho_corasick_automaton(keywords);

julia> Generic.search(aut, [10, 4, 1, 2, 3, 4])
AbstractAlgebra.Generic.AhoCorasickMatch(6, 1, [1, 2, 3, 4])
```
"""
mutable struct AhoCorasickAutomaton
goto::Vector{Dict{Int,Int}}
fail::Vector{Int}
"""
Output stores for each node a tuple (i, k), where i is the index of the keyword k in
the original list of keywords. If several keywords would be the output of the node, only
the one with the smallest index is stored
"""
output::Vector{Tuple{Int,Word}}
end

@doc """
AhoCorasickMatch(last_position::Int, keyword_index::Int, keyword::Vector{Int})

The return value of searching in a given word with an AhoCorasickAutomaton. Contains the position of the last letter in
the word that matches a keyword in the automaton, an index of the keyword that was matched and the keyword itself.

# Examples
```jldoctest; setup = :(using AbstractAlgebra)
julia> keywords = [[1, 2, 3, 4], [1, 5, 4], [4, 1, 2], [1, 2]];

julia> aut = Generic.aho_corasick_automaton(keywords);

julia> result = Generic.search(aut, [10, 4, 1, 2, 3, 4])
AbstractAlgebra.Generic.AhoCorasickMatch(6, 1, [1, 2, 3, 4])

julia> Generic.last_position(result)
6

julia> Generic.keyword_index(result)
1

julia> Generic.keyword(result)
4-element Vector{Int64}:
1
2
3
4
```
"""
struct AhoCorasickMatch
last_position::Int
keyword_index::Int
keyword::Word
end

"""
returns the last position of the match in the word that was searched
"""
function last_position(match::AhoCorasickMatch)
return match.last_position
end

"""
returns the index of the keyword in the corresponding aho corasick automaton
"""
function keyword_index(match::AhoCorasickMatch)
return match.keyword_index
end

"""
returns the keyword corresponding to the match
"""
function keyword(match::AhoCorasickMatch)
return match.keyword
end

function aho_corasick_match(last_position::Int, keyword_index::Int, keyword::Word)
return AhoCorasickMatch(last_position, keyword_index, keyword)

Check warning on line 127 in src/generic/AhoCorasick.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/AhoCorasick.jl#L126-L127

Added lines #L126 - L127 were not covered by tests
end

Base.hash(m::AhoCorasickMatch, h::UInt) = hash(m.last_position, hash(m.keyword_index,
hash(m.keyword, h)))
function ==(m1::AhoCorasickMatch, m2::AhoCorasickMatch)
return m1.last_position == m2.last_position &&
m1.keyword_index == m2.keyword_index &&
m1.keyword == m2.keyword
end

function AhoCorasickAutomaton(keywords::Vector{Word})
automaton = AhoCorasickAutomaton([], [], [])
construct_goto!(automaton, keywords)
construct_fail!(automaton)
return automaton
end

function aho_corasick_automaton(keywords::Vector{Word})
return AhoCorasickAutomaton(keywords)
end

function lookup(automaton::AhoCorasickAutomaton, current_state::Int, next_letter::Int)
ret_value = get(automaton.goto[current_state], next_letter, nothing)
if current_state == 1 && isnothing(ret_value)
return 1
end
return ret_value
end


function Base.length(automaton::AhoCorasickAutomaton)
return length(automaton.goto)

Check warning on line 159 in src/generic/AhoCorasick.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/AhoCorasick.jl#L158-L159

Added lines #L158 - L159 were not covered by tests
end

function new_state!(automaton)
push!(automaton.goto, Dict{Int,Int}())
push!(automaton.output, (typemax(Int), []))
push!(automaton.fail, 1)
return length(automaton.goto)
end

function enter!(automaton::AhoCorasickAutomaton, keyword::Word, current_index)
current_state = 1
for c in keyword
current_state = get!(automaton.goto[current_state], c) do
new_state!(automaton)
end
end
if automaton.output[current_state][1] > current_index
automaton.output[current_state] = (current_index, keyword)
end
end

function construct_goto!(automaton::AhoCorasickAutomaton, keywords::Vector{Word})
new_state!(automaton)
for (current_index, keyword) in enumerate(keywords)
enter!(automaton, keyword, current_index)
end
end

function construct_fail!(automaton::AhoCorasickAutomaton)
q = Queue{Int}()
for v in values(automaton.goto[1])
enqueue!(q, v)
end
while !isempty(q)
current_state = dequeue!(q)
for k in keys(automaton.goto[current_state])
new_state = lookup(automaton, current_state, k)
enqueue!(q, new_state)
state = automaton.fail[current_state]
while isnothing(lookup(automaton, state, k))
state = automaton.fail[state]
end
automaton.fail[new_state] = lookup(automaton, state, k)
if automaton.output[new_state][1] >
automaton.output[automaton.fail[new_state]][1]
automaton.output[new_state] = automaton.output[automaton.fail[new_state]]
end

end
end
end

@doc """
insert_keyword!(aut::AhoCorasickAutomaton, keyword::Word, index::Int)

Insert a new keyword into a given Aho-Corasick automaton to avoid having to rebuild the entire
automaton.
"""
function insert_keyword!(aut::AhoCorasickAutomaton, keyword::Word, index::Int)
enter!(aut, keyword, index)
aut.fail = ones(Int, length(aut.goto))
construct_fail!(aut)
Comment on lines +219 to +221
Copy link
Member

Choose a reason for hiding this comment

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

So you are essentially reconstructing the whole failure function from scratch.

You may have already found this (it's linked on Wikipedia), but just in case not: it seems that there is an incremental version of Aho-Corasick which might be interesting for you (I have no idea if insert_keyword! ever is a bottleneck for you, though). Anyway, see https://se.inf.ethz.ch/~meyer/publications/string/string_matching.pdf -- I did not check this in detail, but the relevant section doesn't seem very long. If this function ever shows up in your profiling, it might be worth experimenting with it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion, that looks nice, however in my computations insert_keyword! is mostly irrelevant in terms of running time. The vast majority of time is spent in normal_form, roughly 95% in some examples and I think the relevance of normal_form increases with the number of relations.

end

@doc """
search(automaton::AhoCorasickAutomaton, word::Word)

Search for the first occurrence of a keyword that is stored in `automaton` in the given `word`.
"""
function search(automaton::AhoCorasickAutomaton, word::Word)
current_state = 1
result = AhoCorasickMatch(typemax(Int), typemax(Int), [])
for i in 1:length(word)
c = word[i]
while true
next_state = lookup(automaton, current_state, c)
if !isnothing(next_state)
current_state = next_state
break
else
current_state = automaton.fail[current_state]
end
end
if automaton.output[current_state][1] < result.keyword_index
result = AhoCorasickMatch(
i,
automaton.output[current_state][1],
automaton.output[current_state][2],
)
end
end
if isempty(result.keyword)
return nothing
end
return result
end
57 changes: 56 additions & 1 deletion src/generic/FreeAssAlgebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,62 @@
return z
end


@doc """
isless(p::FreeAssAlgElem{T}, q::FreeAssAlgElem{T}) where T

Implements the degree lexicographic ordering on terms, i.e.
first, the degrees of the largest monomials are compared, and if they
are the same, they are compared lexicographically and if they are still the same,
the coefficients are compared.
If everything is still the same, the next largest monomial is compared
and lastly the number of monomials is compared.
Since the coefficients are also compared, this only works when the
coefficient Ring implements isless.

The order of letters is the reverse of the order given when initialising the algebra.

# Examples
```jldoctest; setup = :(using AbstractAlgebra)
julia> R, (x, y) = free_associative_algebra(QQ, ["x", "y"]);

julia> x < y^2
true

julia> x^2 < x^2 + y
true

julia> y < x
true

julia> x^2 < 2*x^2
true
```
"""
function isless(p::FreeAssAlgElem{T}, q::FreeAssAlgElem{T}) where T
julien-schanz marked this conversation as resolved.
Show resolved Hide resolved
if p == q
return false
end
l = min(length(p.exps), length(q.exps))
sort_terms!(p)
sort_terms!(q)
for i in 1:l
c = word_cmp(q.exps[i], p.exps[i])
if c > 0
return true
elseif c < 0
return false
elseif p.coeffs[i] != q.coeffs[i]
return p.coeffs[i] < q.coeffs[i]
end
end
if length(p.exps) < length(q.exps)
return true
else
return false

Check warning on line 425 in src/generic/FreeAssAlgebra.jl

View check run for this annotation

Codecov / codecov/patch

src/generic/FreeAssAlgebra.jl#L425

Added line #L425 was not covered by tests
end
end

###############################################################################
#
# Arithmetic
Expand Down Expand Up @@ -511,7 +567,6 @@
return FreeAssAlgElem{T}(parent(a), zcoeffs, zexps, a.length)
end


# return (true, l, r) with a = l*b*r and length(l) minimal
# or (false, junk, junk) if a is not two-sided divisible by b
function word_divides_leftmost(a::Vector{Int}, b::Vector{Int})
Expand Down
Loading
Loading