diff --git a/src/Groups.jl b/src/Groups.jl index 9d60ad1..fcfdd3e 100644 --- a/src/Groups.jl +++ b/src/Groups.jl @@ -23,6 +23,7 @@ include("findreplace.jl") module New include("new_types.jl") +include("new_hashing.jl") include("normalform.jl") end # module New diff --git a/src/new_hashing.jl b/src/new_hashing.jl new file mode 100644 index 0000000..20dceab --- /dev/null +++ b/src/new_hashing.jl @@ -0,0 +1,49 @@ +## Hashing + +_hashing_data(g::FPGroupElement) = word(g) + +bitget(h::UInt, n::Int) = Bool((h & (1 << n)) >> n) +bitclear(h::UInt, n::Int) = h & ~(1 << n) +bitset(h::UInt, n::Int) = h | (1 << n) +bitset(h::UInt, v::Bool, n::Int) = v ? bitset(h, n) : bitclear(h, n) + +# We store hash of a word in field `savedhash` to use it as cheap proxy to +# determine non-equal words. Additionally bits of `savehash` store boolean +# information as follows +# * `savedhash & 1` (the first bit): is word in normal form? +# * `savedhash & 2` (the second bit): is the hash valid? +const __BITFLAGS_MASK = ~(~(UInt(0)) << 2) + +isnormalform(g::FPGroupElement) = bitget(g.savedhash, 0) +_isvalidhash(g::FPGroupElement) = bitget(g.savedhash, 1) + +_setnormalform(h::UInt, v::Bool) = bitset(h, v, 0) +_setvalidhash(h::UInt, v::Bool) = bitset(h, v, 1) + +_setnormalform!(g::FPGroupElement, v::Bool) = + g.savedhash = _setnormalform(g.savedhash, v) +_setvalidhash!(g::FPGroupElement, v::Bool) = + g.savedhash = _setvalidhash(g.savedhash, v) + +# To update hash use this internal method, possibly only after computing the +# normal form of `g`: +function _update_savedhash!(g::FPGroupElement, data=_hashing_data(g)) + h = hash(data, hash(parent(g))) + h = (h << count_ones(__BITFLAGS_MASK)) | (__BITFLAGS_MASK & g.savedhash) + g.savedhash = _setvalidhash(h, true) + return g +end + +function Base.hash(g::FPGroupElement, h::UInt) + normalform!(g) + _isvalidhash(g) || _update_savedhash!(g) + return hash(g.savedhash >> count_ones(__BITFLAGS_MASK) , h) +end + +function Base.copyto!(res::FPGroupElement, g::FPGroupElement) + @assert parent(res) === parent(g) + resize!(word(res), length(word(g))) + copyto!(word(res), word(g)) + res.savedhash = g.savedhash + return res +end diff --git a/src/new_types.jl b/src/new_types.jl index 6bd36e3..d05b030 100644 --- a/src/new_types.jl +++ b/src/new_types.jl @@ -132,38 +132,6 @@ function Base.show(io::IO, f::FPGroupElement) print(io, KnuthBendix.string_repr(word(f), alphabet(f))) end -## Hashing - -# We store hash of a word in field `savedhash` to use it as cheap replacement -# for equality checking. In the last bit of `savehash` we store the information -# whether the word is already reduced (in normal form) or not. Hence -isnormalform(g::FPGroupElement) = Bool(g.savedhash & 1) - -# To update hash use this internal method, possibly only after computing the -# normal form of `g`: -_update_savedhash!(g::FPGroupElement, h = hash(word(g))) = - (g.savedhash = h | 1; return g) - -# If `g` has been mutated (e.g. `word(g)` has been modified, `_invalidate_hash` -# must be called. -_invalidate_hash!(g::FPGroupElement) = g.savedhash = g.savedhash & 0 - -# Accessor for the saved hash -@inline _savedhash(f::FPGroupElement) = f.savedhash - -function Base.hash(g::FPGroupElement, h::UInt) - normalform!(g) - # compute the best approximation of the normal form - return hash(parent(g), _savedhash(g) ⊻ h) -end - -function Base.copyto!(res::FPGroupElement, g::FPGroupElement) - resize!(word(res), length(word(g))) - copyto!(word(res), word(g)) - _invalidate_hash!(res) - return res -end - ## GroupElement Interface for FPGroupElement Base.parent(f::FPGroupElement) = f.parent @@ -171,13 +139,14 @@ GroupsCore.parent_type(::Type{<:FPGroupElement{G}}) where G = G function Base.:(==)(g::FPGroupElement, h::FPGroupElement) @boundscheck @assert parent(g) === parent(h) + normalform!(g) + normalform!(h) hash(g) != hash(h) && return false - # hash reduces to normal form behind the scenes return word(g) == word(h) end function Base.deepcopy_internal(g::FPGroupElement, stackdict::IdDict) - return FPGroupElement(copy(word(g)), _savedhash(g), parent(g)) + return FPGroupElement(copy(word(g)), g.savedhash, parent(g)) end Base.inv(g::FPGroupElement) = diff --git a/src/normalform.jl b/src/normalform.jl index 76ef5dd..31cde79 100644 --- a/src/normalform.jl +++ b/src/normalform.jl @@ -11,7 +11,8 @@ Compute the normal form of `g`, possibly modifying `g` in-place. copyto!(word(g), w) end - g = _update_savedhash!(g) + _setnormalform!(g, true) + _setvalidhash!(g, false) @assert isnormalform(g) return g end @@ -24,11 +25,11 @@ function normalform!(res::GEl, g::GEl) where GEl<:FPGroupElement @boundscheck @assert parent(res) === parent(g) if isnormalform(g) copyto!(res, g) - _update_savedhash!(res, g.savedhash) else resize!(word(res), 0) normalform!(word(res), g) - res = _update_savedhash!(res) + _setnormalform!(res, true) + _setvalidhash!(res, false) end return res end