diff --git a/data/all/2/small_2.g.gz b/data/all/2/small_2.g.gz index cd118ba..676b17a 100644 Binary files a/data/all/2/small_2.g.gz and b/data/all/2/small_2.g.gz differ diff --git a/data/all/3/small_3.g.gz b/data/all/3/small_3.g.gz index 7f10adc..152d743 100644 Binary files a/data/all/3/small_3.g.gz and b/data/all/3/small_3.g.gz differ diff --git a/data/all/4/small_4.g.gz b/data/all/4/small_4.g.gz index a762837..1372737 100644 Binary files a/data/all/4/small_4.g.gz and b/data/all/4/small_4.g.gz differ diff --git a/data/non-isomorphic/2/small_2.g.gz b/data/non-isomorphic/2/small_2.g.gz index cd118ba..676b17a 100644 Binary files a/data/non-isomorphic/2/small_2.g.gz and b/data/non-isomorphic/2/small_2.g.gz differ diff --git a/data/non-isomorphic/3/small_3.g.gz b/data/non-isomorphic/3/small_3.g.gz index a3b4373..c4293e9 100644 Binary files a/data/non-isomorphic/3/small_3.g.gz and b/data/non-isomorphic/3/small_3.g.gz differ diff --git a/data/non-isomorphic/4/small_4.g.gz b/data/non-isomorphic/4/small_4.g.gz index 40522b2..5107261 100644 Binary files a/data/non-isomorphic/4/small_4.g.gz and b/data/non-isomorphic/4/small_4.g.gz differ diff --git a/lib/helper.gd b/lib/helper.gd index c59f254..9fcbc51 100644 --- a/lib/helper.gd +++ b/lib/helper.gd @@ -25,15 +25,29 @@ __SmallAntimagmaHelper.checkOrderId := function(order, id) __SmallAntimagmaHelper.checkOrder(id); end; +__SmallAntimagmaHelper.checkNonnegativeIntegerList := function(values) + if not IsList(values) then + ErrorNoReturn("smallantimagmas: ", " must be a list"); + fi; + + if not ForAll(values, value -> IsInt(value) and value >= 0) then + ErrorNoReturn("smallantimagmas: ", + " must contain only nonnegative integers"); + fi; +end; + __SmallAntimagmaHelper.getSmallAntimagmaMetadataDirectory := function(order) local result; __SmallAntimagmaHelper.checkOrder(order); - result := DirectoriesPackageLibrary("smallantimagmas", Concatenation(["data", "/", "non-isomorphic", "/", String(order)])); + result := DirectoriesPackageLibrary("smallantimagmas", + Concatenation(["data", "/", "non-isomorphic", "/", + String(order)])); if Size(result) = 0 then ErrorNoReturn("smallantimagmas:", " is not yet implemeneted"); fi; if Size(result) > 1 then - ErrorNoReturn("smallantimagmas:", "metadata directory must not be ambigous"); + ErrorNoReturn("smallantimagmas:", + "metadata directory must not be ambiguous"); fi; return First(result); end; @@ -45,15 +59,18 @@ __SmallAntimagmaHelper.getSmallAntimagmaMetadata := function(order) return ReadAsFunction(First(files)); end; -__SmallAntimagmaHelper.getAllSmallAntimagmaMetadataDirectory := function(order) +__SmallAntimagmaHelper.getAllSmallAntimagmaMetadataDirectory := + function(order) local result; __SmallAntimagmaHelper.checkOrder(order); - result := DirectoriesPackageLibrary("smallantimagmas", Concatenation(["data", "/", "all", "/", String(order)])); + result := DirectoriesPackageLibrary("smallantimagmas", + Concatenation(["data", "/", "all", "/", String(order)])); if Size(result) = 0 then ErrorNoReturn("smallantimagmas:", " is not yet implemeneted"); fi; if Size(result) > 1 then - ErrorNoReturn("smallantimagmas:", "metadata directory must not be ambigous"); + ErrorNoReturn("smallantimagmas:", + "metadata directory must not be ambiguous"); fi; return First(result); end; @@ -65,14 +82,335 @@ __SmallAntimagmaHelper.getAllSmallAntimagmaMetadata := function(order) return ReadAsFunction(First(files)); end; +__SmallAntimagmaHelper.EnumeratorOfTuplesWithCache := + (function() + local enumerators; + enumerators := []; + return function(n) + if not IsBound(enumerators[n]) then + enumerators[n] := EnumeratorOfTuples([1 .. n], n); + fi; + return enumerators[n]; + end; + end)(); + __SmallAntimagmaHelper.MultiplicationTableConvert := function(T) - local nrows; + local nrows, enum; nrows := NrRows(T); - return List(T, row -> Position(EnumeratorOfTuples([1 .. nrows], nrows), row)); + enum := __SmallAntimagmaHelper.EnumeratorOfTuplesWithCache(nrows); + return List(T, row -> Position(enum, row)); end; __SmallAntimagmaHelper.MultiplicationTableReverse := function(T) - local ncols; + local ncols, enum; ncols := Size(T); - return List(T, col -> EnumeratorOfTuples([1 .. ncols], ncols)[col]); -end; \ No newline at end of file + enum := __SmallAntimagmaHelper.EnumeratorOfTuplesWithCache(ncols); + return List(T, col -> enum[col]); +end; + +# ==================================================================== +# Precomputed powers cache: n^0, n^1, ..., n^(n-1) +# ==================================================================== +__SmallAntimagmaHelper.PowersOfNWithCache := + (function() + local cache; + cache := []; + return function(n) + if not IsBound(cache[n]) then + cache[n] := List([1 .. n], c -> n ^ (c - 1)); + MakeImmutable(cache[n]); + fi; + return cache[n]; + end; + end)(); + +# ==================================================================== +# Precomputed powers cache: (n^n)^0, (n^n)^1, ..., (n^n)^(n-1) +# ==================================================================== +__SmallAntimagmaHelper.PowersOfNNWithCache := + (function() + local cache; + cache := []; + return function(n) + local nn; + if not IsBound(cache[n]) then + nn := n ^ n; + cache[n] := List([1 .. n], r -> nn ^ (r - 1)); + MakeImmutable(cache[n]); + fi; + return cache[n]; + end; + end)(); + +# ==================================================================== +# Row dictionary cache: all n^n possible rows for order n +# GAP arrays are 1-indexed, so row ID 0 is stored at index 1. +# ==================================================================== +__SmallAntimagmaHelper.RowDictWithCache := + (function() + local cache; + cache := []; + return function(n) + local nn, powers_n; + if not IsBound(cache[n]) then + nn := n ^ n; + powers_n := + __SmallAntimagmaHelper.PowersOfNWithCache(n); + cache[n] := List([0 .. nn - 1], i -> + List([1 .. n], + c -> (QuoInt(i, powers_n[c]) mod n) + 1)); + MakeImmutable(cache[n]); + fi; + return cache[n]; + end; + end)(); + +# ==================================================================== +# Encode a multiplication table (list of lists, entries 1..n) to a +# single immediate integer using base-n^n packing. +# ==================================================================== +__SmallAntimagmaHelper.MultiplicationTableEncode := + function(table) + local n, encoded, r, c, row_id, powers_n, powers_nn; + n := Length(table); + powers_n := __SmallAntimagmaHelper.PowersOfNWithCache(n); + powers_nn := __SmallAntimagmaHelper.PowersOfNNWithCache(n); + encoded := 0; + + for r in [1 .. n] do + row_id := 0; + for c in [1 .. n] do + row_id := row_id + + (table[r][c] - 1) * powers_n[c]; + od; + encoded := encoded + row_id * powers_nn[r]; + od; + + return encoded; +end; + +# ==================================================================== +# Decode a single integer back to an n x n multiplication table. +# ==================================================================== +__SmallAntimagmaHelper.MultiplicationTableDecode := + function(encoded, n) + local powers_nn, nn, row_dict; + powers_nn := __SmallAntimagmaHelper.PowersOfNNWithCache(n); + nn := n ^ n; + row_dict := __SmallAntimagmaHelper.RowDictWithCache(n); + return List([1 .. n], function(r) + local row_id; + row_id := QuoInt(encoded, powers_nn[r]) mod nn; + return row_dict[row_id + 1]; + end); +end; + +# ==================================================================== +# O(1) access to entry (r, c) of an encoded table. +# r and c are 1-indexed (1 to n). +# ==================================================================== +__SmallAntimagmaHelper.MultiplicationTableGetEntry := + function(encoded, n, r, c) + local row_id, powers_nn, nn, row_dict; + powers_nn := __SmallAntimagmaHelper.PowersOfNNWithCache(n); + nn := n ^ n; + row_dict := __SmallAntimagmaHelper.RowDictWithCache(n); + row_id := QuoInt(encoded, powers_nn[r]) mod nn; + return row_dict[row_id + 1][c]; +end; + +__SmallAntimagmaHelper.IntegerLog2Floor := function(n) + local result; + result := 0; + + while n >= 2 do + n := QuoInt(n, 2); + result := result + 1; + od; + + return result; +end; + +__SmallAntimagmaHelper.EliasFanoIndexOfSet := function(values) + local set_values, size, universe, lower_bits_count, + lower_base, lower_mask, lower_parts, upper_positions, + i, high_part, result; + + __SmallAntimagmaHelper.checkNonnegativeIntegerList(values); + + set_values := Set(values); + size := Length(set_values); + + if size = 0 then + return rec( + size := 0, + universe := 0, + lowerBitsCount := 0, + lowerBase := 1, + lowerMask := 0, + lowerParts := [], + upperPositions := []); + fi; + + universe := Maximum(set_values) + 1; + if universe <= size then + lower_bits_count := 0; + else + lower_bits_count := __SmallAntimagmaHelper.IntegerLog2Floor( + QuoInt(universe, size)); + fi; + + lower_base := 2 ^ lower_bits_count; + lower_mask := lower_base - 1; + lower_parts := List(set_values, value -> value mod lower_base); + upper_positions := []; + + for i in [1 .. size] do + high_part := QuoInt(set_values[i], lower_base); + Add(upper_positions, high_part + i); + od; + + MakeImmutable(lower_parts); + MakeImmutable(upper_positions); + + result := rec( + size := size, + universe := universe, + lowerBitsCount := lower_bits_count, + lowerBase := lower_base, + lowerMask := lower_mask, + lowerParts := lower_parts, + upperPositions := upper_positions); + MakeImmutable(result); + return result; +end; + +__SmallAntimagmaHelper.EliasFanoIndexGet := function(index, position) + local high_part; + + if not IsInt(position) or position < 1 or position > index.size then + ErrorNoReturn("smallantimagmas: ", + " must be an integer between 1 and index.size"); + fi; + + high_part := index.upperPositions[position] - position; + return high_part * index.lowerBase + index.lowerParts[position]; +end; + +__SmallAntimagmaHelper.EliasFanoIndexDecode := function(index) + return List([1 .. index.size], + position -> __SmallAntimagmaHelper.EliasFanoIndexGet( + index, position)); +end; + +__SmallAntimagmaHelper.EliasFanoIndexContains := function(index, value) + local left, right, middle, current; + + if not IsInt(value) or value < 0 then + return false; + fi; + + left := 1; + right := index.size; + + while left <= right do + middle := QuoInt(left + right, 2); + current := __SmallAntimagmaHelper.EliasFanoIndexGet(index, middle); + + if current = value then + return true; + elif current < value then + left := middle + 1; + else + right := middle - 1; + fi; + od; + + return false; +end; + +__SmallAntimagmaHelper.EliasFanoIndexOfList := function(values) + local sorted_values, positions, result; + + __SmallAntimagmaHelper.checkNonnegativeIntegerList(values); + sorted_values := Set(values); + positions := List(values, + value -> PositionSorted(sorted_values, value)); + MakeImmutable(positions); + + result := rec( + size := Length(values), + index := __SmallAntimagmaHelper.EliasFanoIndexOfSet(sorted_values), + positions := positions); + MakeImmutable(result); + return result; +end; + +__SmallAntimagmaHelper.EliasFanoIndexListGet := function(index, position) + if not IsInt(position) or position < 1 or position > index.size then + ErrorNoReturn("smallantimagmas: ", + " must be an integer between 1 and index.size"); + fi; + + return __SmallAntimagmaHelper.EliasFanoIndexGet(index.index, + index.positions[position]); +end; + +__SmallAntimagmaHelper.EliasFanoIndexListDecode := function(index) + return List([1 .. index.size], + position -> __SmallAntimagmaHelper.EliasFanoIndexListGet( + index, position)); +end; + +__SmallAntimagmaHelper.IsEliasFanoSetIndex := function(index) + return IsRecord(index) + and IsBound(index.size) + and IsBound(index.universe) + and IsBound(index.lowerBitsCount) + and IsBound(index.lowerBase) + and IsBound(index.lowerMask) + and IsBound(index.lowerParts) + and IsBound(index.upperPositions); +end; + +__SmallAntimagmaHelper.IsEliasFanoListIndex := function(index) + return IsRecord(index) + and IsBound(index.size) + and IsBound(index.index) + and IsBound(index.positions) + and __SmallAntimagmaHelper.IsEliasFanoSetIndex(index.index); +end; + +__SmallAntimagmaHelper.MetadataIndexFromData := function(metadata) + if __SmallAntimagmaHelper.IsEliasFanoListIndex(metadata) then + return metadata; + fi; + + return __SmallAntimagmaHelper.EliasFanoIndexOfList(metadata); +end; + +__SmallAntimagmaHelper.getSmallAntimagmaMetadataIndex := + (function() + local cache; + cache := []; + return function(order) + if not IsBound(cache[order]) then + cache[order] := __SmallAntimagmaHelper.MetadataIndexFromData( + __SmallAntimagmaHelper.getSmallAntimagmaMetadata(order)()); + fi; + return cache[order]; + end; + end)(); + +__SmallAntimagmaHelper.getAllSmallAntimagmaMetadataIndex := + (function() + local cache; + cache := []; + return function(order) + if not IsBound(cache[order]) then + cache[order] := __SmallAntimagmaHelper.MetadataIndexFromData( + __SmallAntimagmaHelper.getAllSmallAntimagmaMetadata(order)()); + fi; + return cache[order]; + end; + end)(); \ No newline at end of file diff --git a/lib/smallantimagmas.gi b/lib/smallantimagmas.gi index 04ef489..3f275e3 100644 --- a/lib/smallantimagmas.gi +++ b/lib/smallantimagmas.gi @@ -1,24 +1,39 @@ InstallGlobalFunction(NrSmallAntimagmas, function(order) - return Size(__SmallAntimagmaHelper.getSmallAntimagmaMetadata(order)()); + return __SmallAntimagmaHelper.getSmallAntimagmaMetadataIndex(order).size; end); InstallGlobalFunction(SmallAntimagma, function(order, id) return MagmaByMultiplicationTable( - __SmallAntimagmaHelper.MultiplicationTableReverse(__SmallAntimagmaHelper.getSmallAntimagmaMetadata(order)()[id])); + __SmallAntimagmaHelper.MultiplicationTableDecode( + __SmallAntimagmaHelper.EliasFanoIndexListGet( + __SmallAntimagmaHelper.getSmallAntimagmaMetadataIndex( + order), id), + order)); end); InstallGlobalFunction(AllSmallAntimagmas, function(order) if IsList(order) and ForAll(order, o -> IsInt(o)) then return Flat( - List(order, o -> List(__SmallAntimagmaHelper.getSmallAntimagmaMetadata(o)(), - table -> MagmaByMultiplicationTable( - __SmallAntimagmaHelper.MultiplicationTableReverse(table))))); + List(order, o -> List( + __SmallAntimagmaHelper.EliasFanoIndexListDecode( + __SmallAntimagmaHelper + .getSmallAntimagmaMetadataIndex(o)), + table -> MagmaByMultiplicationTable( + __SmallAntimagmaHelper + .MultiplicationTableDecode( + table, o))))); elif IsInt(order) then - return List(__SmallAntimagmaHelper.getSmallAntimagmaMetadata(order)(), table -> MagmaByMultiplicationTable( - __SmallAntimagmaHelper.MultiplicationTableReverse(table))); + return List( + __SmallAntimagmaHelper.EliasFanoIndexListDecode( + __SmallAntimagmaHelper + .getSmallAntimagmaMetadataIndex(order)), + table -> MagmaByMultiplicationTable( + __SmallAntimagmaHelper + .MultiplicationTableDecode( + table, order))); fi; end); @@ -36,19 +51,30 @@ end); InstallGlobalFunction(ReallyNrSmallAntimagmas, function(order) - return Size(__SmallAntimagmaHelper.getAllSmallAntimagmaMetadata(order)()); + return __SmallAntimagmaHelper + .getAllSmallAntimagmaMetadataIndex(order).size; end); InstallGlobalFunction(ReallyAllSmallAntimagmas, function(order) if IsList(order) and ForAll(order, o -> IsInt(o)) then return Flat( - List(order, o -> List(__SmallAntimagmaHelper.getAllSmallAntimagmaMetadata(o)(), - table -> MagmaByMultiplicationTable( - __SmallAntimagmaHelper.MultiplicationTableReverse(table))))); + List(order, o -> List( + __SmallAntimagmaHelper.EliasFanoIndexListDecode( + __SmallAntimagmaHelper + .getAllSmallAntimagmaMetadataIndex(o)), + table -> MagmaByMultiplicationTable( + __SmallAntimagmaHelper + .MultiplicationTableDecode( + table, o))))); elif IsInt(order) then - return List(__SmallAntimagmaHelper.getAllSmallAntimagmaMetadata(order)(), - table -> MagmaByMultiplicationTable( - __SmallAntimagmaHelper.MultiplicationTableReverse(table))); + return List( + __SmallAntimagmaHelper.EliasFanoIndexListDecode( + __SmallAntimagmaHelper + .getAllSmallAntimagmaMetadataIndex(order)), + table -> MagmaByMultiplicationTable( + __SmallAntimagmaHelper + .MultiplicationTableDecode( + table, order))); fi; end); \ No newline at end of file diff --git a/lib/utils.gi b/lib/utils.gi index 1aa5f96..c30d98b 100644 --- a/lib/utils.gi +++ b/lib/utils.gi @@ -1,6 +1,8 @@ InstallMethod(AntimagmaGeneratorPossibleDiagonals, "for possible antiassociative diagonals", [IsPosInt], function(n) - return Filtered(EnumeratorOfTuples([1 .. n], n), t -> ForAll([1 .. n], i -> t[i] <> i)); + local enum; + enum := EnumeratorOfTuples([1 .. n], n); + return Filtered(enum, t -> ForAll([1 .. n], i -> t[i] <> i)); end); InstallMethod(UpToIsomorphism, "for a list of non-equivalent antimagmas", [IsList], diff --git a/tst/test_helper_elias_fano_index.tst b/tst/test_helper_elias_fano_index.tst new file mode 100644 index 0000000..70ef077 --- /dev/null +++ b/tst/test_helper_elias_fano_index.tst @@ -0,0 +1,56 @@ +gap> START_TEST("test_helper_elias_fano_index.tst"); + +gap> ef := __SmallAntimagmaHelper.EliasFanoIndexOfSet([5, 3]);; + +gap> __SmallAntimagmaHelper.EliasFanoIndexDecode(ef); +[ 3, 5 ] + +gap> List([1 .. ef.size], i -> __SmallAntimagmaHelper.EliasFanoIndexGet(ef, i)); +[ 3, 5 ] + +gap> __SmallAntimagmaHelper.EliasFanoIndexContains(ef, 3); +true + +gap> __SmallAntimagmaHelper.EliasFanoIndexContains(ef, 4); +false + +gap> ef_list := __SmallAntimagmaHelper.EliasFanoIndexOfList([5, 3]);; + +gap> __SmallAntimagmaHelper.EliasFanoIndexListDecode(ef_list); +[ 5, 3 ] + +gap> List([1 .. ef_list.size], i -> __SmallAntimagmaHelper.EliasFanoIndexListGet(ef_list, i)); +[ 5, 3 ] + +gap> ForAll([2 .. 4], function(n) +> local metadata, index; +> metadata := __SmallAntimagmaHelper.getSmallAntimagmaMetadata(n)(); +> index := __SmallAntimagmaHelper.EliasFanoIndexOfSet(metadata); +> return __SmallAntimagmaHelper.EliasFanoIndexDecode(index) = Set(metadata); +> end); +true + +gap> ForAll([2 .. 4], function(n) +> local metadata, index; +> metadata := __SmallAntimagmaHelper.getSmallAntimagmaMetadata(n)(); +> index := __SmallAntimagmaHelper.EliasFanoIndexOfSet(metadata); +> return ForAll(metadata, +> value -> __SmallAntimagmaHelper.EliasFanoIndexContains(index, value)); +> end); +true + +gap> ForAll([2 .. 4], function(n) +> local metadata, index; +> metadata := __SmallAntimagmaHelper.getSmallAntimagmaMetadata(n)(); +> index := __SmallAntimagmaHelper.EliasFanoIndexOfList(metadata); +> return __SmallAntimagmaHelper.EliasFanoIndexListDecode(index) = metadata; +> end); +true + +gap> NrSmallAntimagmas(2); +2 + +gap> ReallyNrSmallAntimagmas(3); +52 + +gap> STOP_TEST("test_helper_elias_fano_index.tst"); diff --git a/tst/test_helper_multiplication_table_encoding.tst b/tst/test_helper_multiplication_table_encoding.tst new file mode 100644 index 0000000..bb03f1a --- /dev/null +++ b/tst/test_helper_multiplication_table_encoding.tst @@ -0,0 +1,156 @@ +gap> START_TEST("test_helper_multiplication_table_encoding.tst"); + +# ==================================================================== +# PowersOfNWithCache +# ==================================================================== +gap> __SmallAntimagmaHelper.PowersOfNWithCache(2); +[ 1, 2 ] + +gap> __SmallAntimagmaHelper.PowersOfNWithCache(3); +[ 1, 3, 9 ] + +gap> __SmallAntimagmaHelper.PowersOfNWithCache(4); +[ 1, 4, 16, 64 ] + +gap> __SmallAntimagmaHelper.PowersOfNWithCache(5); +[ 1, 5, 25, 125, 625 ] + +# ==================================================================== +# PowersOfNNWithCache +# ==================================================================== +gap> __SmallAntimagmaHelper.PowersOfNNWithCache(2); +[ 1, 4 ] + +gap> __SmallAntimagmaHelper.PowersOfNNWithCache(3); +[ 1, 27, 729 ] + +gap> __SmallAntimagmaHelper.PowersOfNNWithCache(4); +[ 1, 256, 65536, 16777216 ] + +gap> __SmallAntimagmaHelper.PowersOfNNWithCache(5); +[ 1, 3125, 9765625, 30517578125, 95367431640625 ] + +# ==================================================================== +# RowDictWithCache: verify dictionary sizes +# ==================================================================== +gap> Length(__SmallAntimagmaHelper.RowDictWithCache(2)); +4 + +gap> Length(__SmallAntimagmaHelper.RowDictWithCache(3)); +27 + +gap> Length(__SmallAntimagmaHelper.RowDictWithCache(4)); +256 + +# ==================================================================== +# RowDictWithCache: verify specific entries (row ID 0 -> all ones) +# ==================================================================== +gap> __SmallAntimagmaHelper.RowDictWithCache(2)[1]; +[ 1, 1 ] + +gap> __SmallAntimagmaHelper.RowDictWithCache(3)[1]; +[ 1, 1, 1 ] + +gap> __SmallAntimagmaHelper.RowDictWithCache(4)[1]; +[ 1, 1, 1, 1 ] + +# ==================================================================== +# MultiplicationTableEncode: encode known tables +# ==================================================================== +gap> __SmallAntimagmaHelper.MultiplicationTableEncode( +> [[2, 1], [2, 1]]); +5 + +gap> __SmallAntimagmaHelper.MultiplicationTableEncode( +> [[2, 2], [1, 1]]); +3 + +# ==================================================================== +# MultiplicationTableDecode: decode and verify round-trip +# ==================================================================== +gap> __SmallAntimagmaHelper.MultiplicationTableDecode(5, 2); +[ [ 2, 1 ], [ 2, 1 ] ] + +gap> __SmallAntimagmaHelper.MultiplicationTableDecode(3, 2); +[ [ 2, 2 ], [ 1, 1 ] ] + +# ==================================================================== +# Round-trip: Encode then Decode for all antimagmas n=2..3 +# ==================================================================== +gap> ForAll(AllSmallAntimagmas([2 .. 3]), function(M) +> local T, enc; +> T := MultiplicationTable(M); +> enc := __SmallAntimagmaHelper.MultiplicationTableEncode(T); +> return __SmallAntimagmaHelper.MultiplicationTableDecode( +> enc, Size(M)) = T; +> end); +true + +# ==================================================================== +# Round-trip: Encode then Decode for really-all antimagmas n=2..3 +# ==================================================================== +gap> ForAll(ReallyAllSmallAntimagmas([2 .. 3]), function(M) +> local T, enc; +> T := MultiplicationTable(M); +> enc := __SmallAntimagmaHelper.MultiplicationTableEncode(T); +> return __SmallAntimagmaHelper.MultiplicationTableDecode( +> enc, Size(M)) = T; +> end); +true + +# ==================================================================== +# MultiplicationTableGetEntry: O(1) access matches full decode +# ==================================================================== +gap> ForAll(AllSmallAntimagmas([2 .. 3]), function(M) +> local T, enc, n; +> T := MultiplicationTable(M); +> n := Size(M); +> enc := __SmallAntimagmaHelper.MultiplicationTableEncode(T); +> return ForAll([1 .. n], r -> +> ForAll([1 .. n], c -> +> __SmallAntimagmaHelper.MultiplicationTableGetEntry( +> enc, n, r, c) = T[r][c])); +> end); +true + +# ==================================================================== +# Encoding is injective: different tables produce different integers +# ==================================================================== +gap> ForAll([2 .. 3], function(n) +> local encodings; +> encodings := List(AllSmallAntimagmas(n), M -> +> __SmallAntimagmaHelper.MultiplicationTableEncode( +> MultiplicationTable(M))); +> return Size(Set(encodings)) = Size(encodings); +> end); +true + +# ==================================================================== +# Encoding 0 always corresponds to the all-ones table +# ==================================================================== +gap> ForAll([2 .. 4], n -> +> __SmallAntimagmaHelper.MultiplicationTableDecode(0, n) +> = List([1 .. n], r -> List([1 .. n], c -> 1))); +true + +# ==================================================================== +# Equivalence: old Convert/Reverse and new Encode/Decode +# produce the same multiplication tables +# ==================================================================== +gap> ForAll(AllSmallAntimagmas([2 .. 3]), function(M) +> local T, old_encoded, old_decoded, new_encoded, new_decoded; +> T := MultiplicationTable(M); +> old_encoded := __SmallAntimagmaHelper +> .MultiplicationTableConvert(T); +> old_decoded := __SmallAntimagmaHelper +> .MultiplicationTableReverse(old_encoded); +> new_encoded := __SmallAntimagmaHelper +> .MultiplicationTableEncode(T); +> new_decoded := __SmallAntimagmaHelper +> .MultiplicationTableDecode( +> new_encoded, Size(M)); +> return old_decoded = new_decoded; +> end); +true + +gap> STOP_TEST("test_helper_multiplication_table_encoding.tst");