mapFolding 0.17.0__py3-none-any.whl → 0.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. easyRun/NOTcountingFolds.py +16 -6
  2. easyRun/__init__.py +1 -0
  3. easyRun/countFolds.py +12 -5
  4. easyRun/eliminateFolds.py +60 -0
  5. mapFolding/__init__.py +2 -1
  6. mapFolding/_theTypes.py +0 -1
  7. mapFolding/algorithms/A086345.py +8 -3
  8. mapFolding/algorithms/__init__.py +1 -1
  9. mapFolding/algorithms/constraintPropagation.py +184 -0
  10. mapFolding/algorithms/elimination.py +131 -0
  11. mapFolding/algorithms/eliminationCount.py +26 -0
  12. mapFolding/algorithms/eliminationPinned.py +35 -0
  13. mapFolding/algorithms/iff.py +206 -0
  14. mapFolding/algorithms/patternFinder.py +280 -0
  15. mapFolding/algorithms/pinning2Dn.py +345 -0
  16. mapFolding/algorithms/pinning2DnAnnex.py +43 -0
  17. mapFolding/basecamp.py +72 -18
  18. mapFolding/beDRY.py +14 -1
  19. mapFolding/dataBaskets.py +56 -0
  20. mapFolding/someAssemblyRequired/transformationTools.py +1 -1
  21. mapFolding/tests/conftest.py +1 -1
  22. mapFolding/tests/test_computations.py +17 -26
  23. mapFolding/tests/verify.py +323 -0
  24. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/METADATA +6 -3
  25. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/RECORD +29 -24
  26. easyRun/A000682.py +0 -25
  27. easyRun/A005316.py +0 -20
  28. mapFolding/algorithms/A000136constraintPropagation.py +0 -95
  29. mapFolding/algorithms/A000136elimination.py +0 -163
  30. mapFolding/algorithms/A000136eliminationParallel.py +0 -77
  31. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/WHEEL +0 -0
  32. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/entry_points.txt +0 -0
  33. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/licenses/LICENSE +0 -0
  34. {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,206 @@
1
+ """Verify that a folding sequence is possible.
2
+
3
+ Notes
4
+ -----
5
+ Eight forbidden inequalities of matching parity k and r *à la* Koehler (1968), indices of:
6
+ [k < r < k+1 < r+1] [r < k+1 < r+1 < k] [k+1 < r+1 < k < r] [r+1 < k < r < k+1]
7
+ [r < k < r+1 < k+1] [k < r+1 < k+1 < r] [r+1 < k+1 < r < k] [k+1 < r < k < r+1]
8
+
9
+ Four forbidden inequalities of matching parity k and r *à la* Legendre (2014), indices of:
10
+ [k < r < k+1 < r+1] [k+1 < r+1 < k < r] [r+1 < k < r < k+1] [k < r+1 < k+1 < r]
11
+
12
+ Citations
13
+ ---------
14
+ - John E. Koehler, Folding a strip of stamps, Journal of Combinatorial Theory, Volume 5, Issue 2, 1968, Pages 135-152, ISSN
15
+ 0021-9800, https://doi.org/10.1016/S0021-9800(68)80048-1.
16
+ - Stéphane Legendre, Foldings and meanders, The Australasian Journal of Combinatorics, Volume 58, Part 2, 2014, Pages 275-291,
17
+ ISSN 2202-3518, https://ajc.maths.uq.edu.au/pdf/58/ajc_v58_p275.pdf.
18
+
19
+ See Also
20
+ --------
21
+ - "[Annotated, corrected, scanned copy]" of Koehler (1968) at https://oeis.org/A001011.
22
+ - Citations in BibTeX format "mapFolding/citations".
23
+ """
24
+ from collections.abc import Callable
25
+ from cytoolz.curried import get
26
+ from cytoolz.functoolz import curry as syntacticCurry
27
+ from functools import cache
28
+ from itertools import combinations, filterfalse, product as CartesianProduct
29
+ from mapFolding import getLeavesTotal
30
+ from mapFolding.dataBaskets import EliminationState
31
+ from math import prod
32
+ from operator import indexOf
33
+
34
+ def thisIsAViolation(column: int, columnComparand: int, getLeafNextCrease: Callable[[], int | None], getComparandNextCrease: Callable[[], int | None], columnOf: Callable[[int], int | None]) -> bool: # noqa: PLR0911
35
+ """Validate.
36
+
37
+ Mathematical reasons for the design of this function
38
+ ----------------------------------------------------
39
+
40
+ 1. To confirm that a multidimensional folding is valid, confirm that each of the constituent one-dimensional¹ foldings is valid.
41
+ 2. To confirm that a one-dimensional folding is valid, check that all creases that might cross do not cross.
42
+
43
+ A "crease" is a convenient lie: it is a shorthand description of two leaves that are physically connected to each other.
44
+ Leaves in a one-dimensional folding are physically connected to at most two other leaves: the prior leaf and the next leaf.
45
+ When talking about a one-dimensional section of a multidimensional folding, we ignore the other dimension and still
46
+ reference the prior and next leaves. To check whether two creases cross, we must compare the four leaves of the two creases.
47
+
48
+ ¹ A so-called one-dimensional folding, map, or strip of stamps has two dimensions, but one of the dimensions has a width of 1.
49
+
50
+ Idiosyncratic reasons for the design of this function
51
+ -----------------------------------------------------
52
+
53
+ I name the first leaf of the first crease `leaf`. I name the leaf to which I am comparing it `comparand`. A crease² is a leaf
54
+ and the next leaf, therefore, the crease of `leaf` connects it to `leafNextCrease`, and the crease of `comparand` connects it
55
+ to `comparandNextCrease`. Nearly everyone else uses letters for names, such as k, k+1, r, and r+1. (Which stand for Kahlo and
56
+ Rivera, of course.)
57
+
58
+ ² "increase" from Latin *in-* "in" + *crescere* "to grow" (from PIE root ⋆ker- "to grow"). https://www.etymonline.com/word/increase
59
+
60
+ Computational reasons for the design of this function
61
+ -----------------------------------------------------
62
+
63
+ If `leaf` and `comparand` do not have matching parity in the dimension, then their creases cannot cross. To call this
64
+ function, you need `leaf` and `comparand`, and because determining parity-by-dimension is easiest when you first select `leaf`
65
+ and `comparand`, this function will not check the parity of `leaf` and `comparand`.
66
+
67
+ Computing the next leaf is not expensive, but 100,000,000 unnecessary but cheap computations is expensive. Therefore, instead of
68
+ passing `leafNextCrease` and `comparandNextCrease`, pass the functions by which those values may be computed on demand.
69
+
70
+ Finally, we need to compare the relative positions of the leaves, so pass a function that returns the position of the "next" leaf.
71
+
72
+ """
73
+ if column < columnComparand:
74
+
75
+ comparandNextCrease: int | None = getComparandNextCrease()
76
+ if comparandNextCrease is None:
77
+ return False
78
+
79
+ leafNextCrease: int | None = getLeafNextCrease()
80
+ if leafNextCrease is None:
81
+ return False
82
+
83
+ columnComparandNextCrease: int | None = columnOf(comparandNextCrease)
84
+ if columnComparandNextCrease is None:
85
+ return False
86
+ columnLeafNextCrease: int | None = columnOf(leafNextCrease)
87
+ if columnLeafNextCrease is None:
88
+ return False
89
+
90
+ if columnComparandNextCrease < column:
91
+ if columnLeafNextCrease < columnComparandNextCrease: # [k+1 < r+1 < k < r]
92
+ return True
93
+ return columnComparand < columnLeafNextCrease # [r+1 < k < r < k+1]
94
+
95
+ if columnComparand < columnLeafNextCrease:
96
+ if columnLeafNextCrease < columnComparandNextCrease: # [k < r < k+1 < r+1]
97
+ return True
98
+ elif column < columnComparandNextCrease < columnLeafNextCrease < columnComparand: # [k < r+1 < k+1 < r]
99
+ return True
100
+ return False
101
+
102
+ # ------- ad hoc computations -----------------------------
103
+ # @cache
104
+ def _dimensionsTotal(mapShape: tuple[int, ...]) -> int:
105
+ return len(mapShape)
106
+
107
+ @cache
108
+ def _leavesTotal(mapShape: tuple[int, ...]) -> int:
109
+ return getLeavesTotal(mapShape)
110
+
111
+ # @cache
112
+ def productOfDimensions(mapShape: tuple[int, ...], dimension: int) -> int:
113
+ return prod(mapShape[0:dimension])
114
+
115
+ # ------- Functions for 'leaf', named 1, 2, ... n, not for 'indexLeaf' -------------
116
+
117
+ @cache
118
+ def ImaOddLeaf(mapShape: tuple[int, ...], leaf: int, dimension: int) -> int:
119
+ # NOTE `leaf-1` because `leaf` is not zero-based indexing.
120
+ return (((leaf-1) // productOfDimensions(mapShape, dimension)) % mapShape[dimension]) & 1
121
+
122
+ def _matchingParityLeaf(mapShape: tuple[int, ...], leaf: int, comparand: int, dimension: int) -> bool:
123
+ return ImaOddLeaf(mapShape, leaf, dimension) == ImaOddLeaf(mapShape, comparand, dimension)
124
+
125
+ @syntacticCurry
126
+ def matchingParityLeaf(mapShape: tuple[int, ...]) -> Callable[[tuple[tuple[tuple[int, int], tuple[int, int]], int]], bool]:
127
+ def repack(aCartesianProduct: tuple[tuple[tuple[int, int], tuple[int, int]], int]) -> bool:
128
+ ((_column, leaf), (_columnComparand, comparand)), dimension = aCartesianProduct
129
+ return _matchingParityLeaf(mapShape, leaf, comparand, dimension)
130
+ return repack
131
+
132
+ @cache
133
+ def nextCreaseLeaf(mapShape: tuple[int, ...], leaf: int, dimension: int) -> int | None:
134
+ leafNext: int | None = None
135
+ if (((leaf-1) // productOfDimensions(mapShape, dimension)) % mapShape[dimension]) + 1 < mapShape[dimension]:
136
+ leafNext = leaf + productOfDimensions(mapShape, dimension)
137
+ return leafNext
138
+
139
+ inThis_pileOf = syntacticCurry(indexOf)
140
+
141
+ def howToGetNextCreaseLeaf(mapShape: tuple[int, ...], leaf: int, dimension: int) -> Callable[[], int | None]:
142
+ return lambda: nextCreaseLeaf(mapShape, leaf, dimension)
143
+
144
+ def thisLeafFoldingIsValid(folding: tuple[int, ...], mapShape: tuple[int, ...]) -> bool:
145
+ """Return `True` if the folding is valid."""
146
+ foldingFiltered: filterfalse[tuple[int, int]] = filterfalse(lambda columnLeaf: columnLeaf[1] == _leavesTotal(mapShape), enumerate(folding)) # leafNPlus1 does not exist.
147
+ leafAndComparand: combinations[tuple[tuple[int, int], tuple[int, int]]] = combinations(foldingFiltered, 2)
148
+
149
+ leafAndComparandAcrossDimensions: CartesianProduct[tuple[tuple[tuple[int, int], tuple[int, int]], int]] = CartesianProduct(leafAndComparand, range(_dimensionsTotal(mapShape)))
150
+ parityInThisDimension: Callable[[tuple[tuple[tuple[int, int], tuple[int, int]], int]], bool] = matchingParityLeaf(mapShape)
151
+ leafAndComparandAcrossDimensionsFiltered: filter[tuple[tuple[tuple[int, int], tuple[int, int]], int]] = filter(parityInThisDimension, leafAndComparandAcrossDimensions)
152
+
153
+ return all(not thisIsAViolation(column, columnComparand, howToGetNextCreaseLeaf(mapShape, leaf, aDimension), howToGetNextCreaseLeaf(mapShape, comparand, aDimension), inThis_pileOf(folding))
154
+ for ((column, leaf), (columnComparand, comparand)), aDimension in leafAndComparandAcrossDimensionsFiltered)
155
+
156
+ # ------- Functions for `indexLeaf`, named 0, 1, ... n-1, not for `leaf` -------------
157
+
158
+ @cache
159
+ def ImaOddIndexLeaf(mapShape: tuple[int, ...], indexLeaf: int, dimension: int) -> int:
160
+ return ((indexLeaf // productOfDimensions(mapShape, dimension)) % mapShape[dimension]) & 1
161
+
162
+ def _matchingParityIndexLeaf(mapShape: tuple[int, ...], indexLeaf: int, comparand: int, dimension: int) -> bool:
163
+ return ImaOddIndexLeaf(mapShape, indexLeaf, dimension) == ImaOddIndexLeaf(mapShape, comparand, dimension)
164
+
165
+ @syntacticCurry
166
+ def matchingParityIndexLeaf(mapShape: tuple[int, ...]) -> Callable[[tuple[tuple[tuple[int, int], tuple[int, int]], int]], bool]:
167
+ def repack(aCartesianProduct: tuple[tuple[tuple[int, int], tuple[int, int]], int]) -> bool:
168
+ ((_pile, indexLeaf), (_pileComparand, comparand)), dimension = aCartesianProduct
169
+ return _matchingParityIndexLeaf(mapShape, indexLeaf, comparand, dimension)
170
+ return repack
171
+
172
+ @cache
173
+ def nextCreaseIndexLeaf(mapShape: tuple[int, ...], indexLeaf: int, dimension: int) -> int | None:
174
+ indexLeafNext: int | None = None
175
+ if ((indexLeaf // productOfDimensions(mapShape, dimension)) % mapShape[dimension]) + 1 < mapShape[dimension]:
176
+ indexLeafNext = indexLeaf + productOfDimensions(mapShape, dimension)
177
+ return indexLeafNext
178
+
179
+ inThis_pileOf = syntacticCurry(indexOf)
180
+
181
+ def getNextCreaseIndexLeaf(mapShape: tuple[int, ...], indexLeaf: int, dimension: int) -> Callable[[], int | None]:
182
+ return lambda: nextCreaseIndexLeaf(mapShape, indexLeaf, dimension)
183
+
184
+ def thisIndexLeafFoldingIsValid(folding: tuple[int, ...], mapShape: tuple[int, ...]) -> bool:
185
+ """Return `True` if the folding is valid."""
186
+ foldingFiltered: filterfalse[tuple[int, int]] = filterfalse(lambda pileIndexLeaf: pileIndexLeaf[1] == _leavesTotal(mapShape) - 1, enumerate(folding)) # indexLeafNPlus1 does not exist.
187
+ indexLeafAndComparand: combinations[tuple[tuple[int, int], tuple[int, int]]] = combinations(foldingFiltered, 2)
188
+
189
+ indexLeafAndComparandAcrossDimensions: CartesianProduct[tuple[tuple[tuple[int, int], tuple[int, int]], int]] = CartesianProduct(indexLeafAndComparand, range(_dimensionsTotal(mapShape)))
190
+ parityInThisDimension: Callable[[tuple[tuple[tuple[int, int], tuple[int, int]], int]], bool] = matchingParityIndexLeaf(mapShape)
191
+ indexLeafAndComparandAcrossDimensionsFiltered: filter[tuple[tuple[tuple[int, int], tuple[int, int]], int]] = filter(parityInThisDimension, indexLeafAndComparandAcrossDimensions)
192
+
193
+ return all(not thisIsAViolation(pile, pileComparand, getNextCreaseIndexLeaf(mapShape, indexLeaf, aDimension), getNextCreaseIndexLeaf(mapShape, comparand, aDimension), inThis_pileOf(folding))
194
+ for ((pile, indexLeaf), (pileComparand, comparand)), aDimension in indexLeafAndComparandAcrossDimensionsFiltered)
195
+
196
+ # ------- Functions for `indexLeaf` in `pinnedLeaves` dictionary, not for `leaf` in `folding` -------------
197
+
198
+ def pinnedLeavesHasAViolation(state: EliminationState, indexLeaf: int) -> bool:
199
+ """Return `True` if `state.pinnedLeaves` or the addition of `indexLeaf` at `state.pile` has a violation."""
200
+ pinnedLeaves: dict[int, int] = state.pinnedLeaves.copy()
201
+ pinnedLeaves[state.pile] = indexLeaf
202
+ indexLeaf2pile: dict[int, int] = {indexLeaf: pile for pile, indexLeaf in pinnedLeaves.items()}
203
+ pinnedLeavesFiltered: filterfalse[tuple[int, int]] = filterfalse(lambda pileIndexLeaf: pileIndexLeaf[1] == state.leavesTotal, pinnedLeaves.items()) # indexLeafNPlus1 does not exist.
204
+ indexLeafAndComparandAcrossDimensions = filter(matchingParityIndexLeaf(state.mapShape), CartesianProduct(combinations(pinnedLeavesFiltered, 2), range(state.dimensionsTotal)))
205
+ return any(thisIsAViolation(pile, pileComparand, getNextCreaseIndexLeaf(state.mapShape, indexLeaf, aDimension), getNextCreaseIndexLeaf(state.mapShape, comparand, aDimension), get(seq=indexLeaf2pile, default=None))
206
+ for ((pile, indexLeaf), (pileComparand, comparand)), aDimension in indexLeafAndComparandAcrossDimensions)
@@ -0,0 +1,280 @@
1
+ # ruff: noqa: ERA001 T201 T203
2
+ from collections.abc import Callable
3
+ from cytoolz.functoolz import curry as syntacticCurry
4
+ from hunterMakesPy import raiseIfNone
5
+ from itertools import repeat
6
+ from mapFolding.dataBaskets import EliminationState
7
+ from math import prod
8
+ from pathlib import Path
9
+ from pprint import pprint
10
+ import gmpy2
11
+ import pandas
12
+
13
+ def ptount(integerAbove3:int, /) -> int:
14
+ """After subtracting 0b000011 from `zz`, measure the distance from a ***p***ower of ***t***wo's bit c***ount***.
15
+
16
+ Notes
17
+ -----
18
+ - Pronounced "tount" because the "p" is silent.
19
+ - Just like the "p", the reason why this is useful is also silent.
20
+ - I suspect there is a more direct route to measure this but I am unaware of it.
21
+ """
22
+ return distanceFromPowerOf2(integerAbove3 - 0b000011).bit_count()
23
+
24
+ def bitBiggest(integerAbove0: int, /) -> int:
25
+ """Find the 0-indexed position of the biggest set bit in `integerAbove0`."""
26
+ return integerAbove0.bit_length() - 1
27
+
28
+ def bitSecond(integerAbove0: int, /) -> int | None:
29
+ """Find the 0-indexed position of the second biggest set bit, if any, in `integerAbove0`."""
30
+ second = bitBiggest(int(gmpy2.bit_flip(integerAbove0, bitBiggest(integerAbove0))))
31
+ return second if second >= 0 else None
32
+
33
+ def distanceFromPowerOf2(integerAbove0: int, /) -> int:
34
+ return int(gmpy2.f_mod_2exp(integerAbove0, integerAbove0.bit_length() - 1))
35
+
36
+ def multiplicityOfPrimeFactor2(integerAbove0: int, /) -> int:
37
+ """Compute the number of times `integerAbove0` is divisible by 2; aka 'CTZ', Count Trailing Zeros in the binary form."""
38
+ return raiseIfNone(gmpy2.bit_scan1(integerAbove0))
39
+
40
+ @syntacticCurry
41
+ def numeralOfLengthInBase(mostSignificantDigits: int | list[int], fillerDigits: str = '0', leastSignificantDigits: int | list[int] = 0, positions: int = 8, base: int = 2) -> int:
42
+ """Prototype."""
43
+ digitsPrefix: tuple[int, ...] = (mostSignificantDigits,) if isinstance(mostSignificantDigits, int) else tuple(mostSignificantDigits)
44
+ quantityPrefix: int = len(digitsPrefix)
45
+
46
+ digitsSuffix: tuple[int, ...] = (leastSignificantDigits,) if isinstance(leastSignificantDigits, int) else tuple(leastSignificantDigits)
47
+ quantitySuffix: int = len(digitsSuffix)
48
+
49
+ quantityFiller: int = positions - quantityPrefix - quantitySuffix
50
+ digitsFiller: tuple[int, ...] = tuple(int(digit) for digit in list(repeat(fillerDigits, quantityFiller))[0:quantityFiller])
51
+
52
+ tupleDigitsMSBtoLSB: tuple[int, ...] = (*digitsPrefix, *digitsFiller, *digitsSuffix)
53
+ digitsAsString: str = ''.join(str(digit) for digit in tupleDigitsMSBtoLSB)
54
+
55
+ numeralAs_int: int = int(digitsAsString, base)
56
+
57
+ return numeralAs_int
58
+
59
+ @syntacticCurry
60
+ def makeFillerDigitsNotation(numeral: int, positions: int = 8, base: int = 2) -> tuple[list[int], str, int | list[int]]:
61
+ """Represent `numeral` as prefix, filler digit, and suffix for reuse with `numeralOfLengthInBase`.
62
+
63
+ (AI generated docstring)
64
+
65
+ This prototype only supports base 2 and expects a non-negative numeral whose binary expansion
66
+ fits inside the specified number of positions. The returned structure abstracts the repeated
67
+ interior digits so that different position counts share the same notation.
68
+ """
69
+ if positions <= 0:
70
+ message: str = f'positions must be positive; received {positions}.'
71
+ raise ValueError(message)
72
+ if base != 2:
73
+ message: str = f'makeFillerDigitsNotation currently supports base 2 only; received base {base}.'
74
+ raise ValueError(message)
75
+ if numeral < 0:
76
+ message: str = f'numeral must be non-negative; received {numeral}.'
77
+ raise ValueError(message)
78
+
79
+ digitsAsString: str = f'{numeral:b}'
80
+ if len(digitsAsString) > positions:
81
+ message: str = f'numeral {numeral} requires {len(digitsAsString)} positions; received {positions}.'
82
+ raise ValueError(message)
83
+ digitsAsString = digitsAsString.zfill(positions)
84
+
85
+ lengthPrefix: int | None = None
86
+ lengthSuffix: int | None = None
87
+ lengthFiller: int = -1
88
+ fillerDigit: str = '0'
89
+
90
+ for prefixLength in range(1, positions):
91
+ for suffixLength in range(1, positions - prefixLength + 1):
92
+ fillerLength: int = positions - prefixLength - suffixLength
93
+ if fillerLength < 0:
94
+ continue
95
+
96
+ if fillerLength == 0:
97
+ candidateFillerDigit: str = digitsAsString[prefixLength - 1]
98
+ else:
99
+ candidateFillerDigit = digitsAsString[prefixLength]
100
+ segmentFiller: str = digitsAsString[prefixLength:prefixLength + fillerLength]
101
+ if segmentFiller != candidateFillerDigit * fillerLength:
102
+ continue
103
+
104
+ if ((lengthPrefix is None)
105
+ or (fillerLength > lengthFiller)
106
+ or ((fillerLength == lengthFiller) and (
107
+ (prefixLength < lengthPrefix)
108
+ or ((prefixLength == lengthPrefix) and (suffixLength < lengthSuffix if lengthSuffix is not None else True))))):
109
+ lengthPrefix = prefixLength
110
+ lengthSuffix = suffixLength
111
+ lengthFiller = fillerLength
112
+ fillerDigit = candidateFillerDigit
113
+
114
+ if lengthPrefix is None or lengthSuffix is None:
115
+ lengthPrefix = positions if positions > 0 else 1
116
+ lengthSuffix = max(0, positions - lengthPrefix)
117
+ lengthFiller = 0
118
+ fillerDigit = digitsAsString[lengthPrefix - 1] if positions > 0 else '0'
119
+
120
+ mostSignificantDigits: list[int] = [int(digit) for digit in digitsAsString[0:lengthPrefix]]
121
+ leastSignificantDigitsSequence: list[int] = [int(digit) for digit in digitsAsString[positions - lengthSuffix:]] if lengthSuffix > 0 else []
122
+ leastSignificantDigits: int | list[int]
123
+ if lengthSuffix == 1:
124
+ leastSignificantDigits = leastSignificantDigitsSequence[0]
125
+ else:
126
+ leastSignificantDigits = leastSignificantDigitsSequence
127
+
128
+ notation: tuple[list[int], str, int | list[int]] = (mostSignificantDigits, fillerDigit, leastSignificantDigits)
129
+ return notation
130
+
131
+ def getDictionaryAddends4Next(state: EliminationState) -> dict[int, list[int]]:
132
+ dictionaryAddends: dict[int, list[int]] = {}
133
+
134
+ indexLeaf: int = 0
135
+ listOfDifferences: list[int] = [1]
136
+ dictionaryAddends[indexLeaf] = listOfDifferences
137
+
138
+ productsOfDimensions: list[int] = [prod(state.mapShape[0:dimension], start=1) for dimension in range(state.dimensionsTotal)]
139
+
140
+ for indexLeaf in range(1, state.leavesTotal):
141
+ products下_indexLeaf: list[int] = productsOfDimensions.copy()
142
+
143
+ theMaskOfDirectionality = gmpy2.bit_mask(state.leavesTotal - 1) & indexLeaf
144
+ for index in range(state.dimensionsTotal):
145
+ if gmpy2.bit_test(theMaskOfDirectionality, index):
146
+ products下_indexLeaf[index] *= -1
147
+
148
+ slicingIndexStart: int = (indexLeaf.bit_count() - 1) & 1 ^ 1
149
+ slicingIndexEnd = (indexLeaf.bit_length() - 1) * (slicingIndexStart ^ 1) or None
150
+
151
+ if (slicingIndexStart == 1) and (gmpy2.is_even(indexLeaf)):
152
+ slicingIndexStart += multiplicityOfPrimeFactor2(indexLeaf)
153
+
154
+ products下_indexLeaf = products下_indexLeaf[slicingIndexStart:None]
155
+ products下_indexLeaf = products下_indexLeaf[0:slicingIndexEnd]
156
+ dictionaryAddends[indexLeaf] = products下_indexLeaf
157
+
158
+ return dictionaryAddends
159
+
160
+ def getDictionaryAddends4Prior(state: EliminationState) -> dict[int, list[int]]:
161
+ dictionaryAddends: dict[int, list[int]] = {}
162
+
163
+ indexLeaf: int = 0
164
+ listOfDifferences: list[int] = []
165
+ dictionaryAddends[indexLeaf] = listOfDifferences
166
+
167
+ indexLeaf = 1
168
+ listOfDifferences = [-1]
169
+ dictionaryAddends[indexLeaf] = listOfDifferences
170
+
171
+ productsOfDimensions: list[int] = [prod(state.mapShape[0:dimension], start=1) for dimension in range(state.dimensionsTotal)]
172
+
173
+ for indexLeaf in range(state.leavesTotal - 1, 1, -1):
174
+ products下_indexLeaf: list[int] = productsOfDimensions.copy()
175
+
176
+ theMaskOfDirectionality = gmpy2.bit_mask(state.leavesTotal - 1) & indexLeaf
177
+ for index in range(state.dimensionsTotal):
178
+ if gmpy2.bit_test(theMaskOfDirectionality, index):
179
+ products下_indexLeaf[index] *= -1
180
+
181
+ slicingIndexStart: int = (indexLeaf.bit_count() - 1) & 1
182
+ slicingIndexEnd = (indexLeaf.bit_length() - 1) * (slicingIndexStart ^ 1) or None
183
+
184
+ if (slicingIndexStart == 1) and (gmpy2.is_even(indexLeaf)):
185
+ slicingIndexStart += multiplicityOfPrimeFactor2(indexLeaf)
186
+
187
+ products下_indexLeaf = products下_indexLeaf[slicingIndexStart:None]
188
+ products下_indexLeaf = products下_indexLeaf[0:slicingIndexEnd]
189
+ dictionaryAddends[indexLeaf] = products下_indexLeaf
190
+
191
+ return dictionaryAddends
192
+
193
+ def getIndexLeafRange(state: EliminationState, indexLeaf: int) -> range:
194
+ return range(indexLeaf.bit_count() + (2**(multiplicityOfPrimeFactor2(indexLeaf) + 1) - 2)
195
+ , state.leavesTotal - (2**(state.dimensionsTotal - indexLeaf.bit_length() + 1) - 2) - (indexLeaf.bit_count() - 1)
196
+ , 2)
197
+
198
+ def getDictionaryIndexLeafRanges(state: EliminationState) -> dict[int, range]:
199
+ """For each `indexLeaf` (not `leaf`), the associated `range` defines
200
+ 1. every `pile` at which `indexLeaf` may be found in a `folding` and
201
+ 2. in the set of all valid foldings, every `pile` at which `indexLeaf` must be found.
202
+ """ # noqa: D205
203
+ dictionaryIndexLeafRanges: dict[int, range] = {indexLeaf: getIndexLeafRange(state, indexLeaf) for indexLeaf in range(1, state.leavesTotal)}
204
+
205
+ indexLeaf: int = 0
206
+ dictionaryIndexLeafRanges[indexLeaf] = range(0, 1, 2)
207
+
208
+ indexLeaf = state.leavesTotal // 2 + 1
209
+ step = 4
210
+ rangeIndexLeaf: range = dictionaryIndexLeafRanges[indexLeaf]
211
+ dictionaryIndexLeafRanges[indexLeaf] = range(rangeIndexLeaf.start, rangeIndexLeaf.stop, step)
212
+
213
+ return dictionaryIndexLeafRanges
214
+
215
+ # TODO
216
+ def getDictionaryPileToLeavesByFormula(state: EliminationState) -> dict[int, list[int]]:
217
+ dictionaryPileToLeaves: dict[int, list[int]] = {pile: [] for pile in range(state.leavesTotal)}
218
+
219
+ return dictionaryPileToLeaves
220
+
221
+ def getDictionaryPileToLeaves(state: EliminationState) -> dict[int, list[int]]:
222
+ dictionaryPileToLeaves: dict[int, list[int]] = {pile: [] for pile in range(state.leavesTotal)}
223
+
224
+ dictionaryLeafRanges: dict[int, range] = getDictionaryIndexLeafRanges(state)
225
+
226
+ for indexLeaf, rangePilings in dictionaryLeafRanges.items():
227
+ for pile in rangePilings:
228
+ dictionaryPileToLeaves[pile].append(indexLeaf)
229
+
230
+ return dictionaryPileToLeaves
231
+
232
+ def _getGroupedBy(state: EliminationState, pileTarget: int, groupByIndexLeavesAtPiles: tuple[int, ...]) -> dict[int | tuple[int, ...], list[int]]:
233
+ pathFilename = Path(f'/apps/mapFolding/Z0Z_notes/arrayFoldingsP2d{state.dimensionsTotal}.pkl')
234
+ arrayFoldings = pandas.read_pickle(pathFilename) # noqa: S301
235
+ dataframeFoldings = pandas.DataFrame(arrayFoldings)
236
+
237
+ groupedBy: dict[int | tuple[int, ...], list[int]] = dataframeFoldings.groupby(list(groupByIndexLeavesAtPiles))[pileTarget].apply(list).to_dict()
238
+ return {indexLeaves: sorted(set(listLeaves)) for indexLeaves, listLeaves in groupedBy.items()}
239
+
240
+ def getExcludedAddendIndices(state: EliminationState, indexLeafAddend: int, pileTarget: int, groupByIndexLeavesAtPiles: tuple[int, ...]) -> dict[int | tuple[int, ...], list[int]]:
241
+ groupedBy: dict[int | tuple[int, ...], list[int]] = _getGroupedBy(state, pileTarget, groupByIndexLeavesAtPiles)
242
+
243
+ dictionaryExclusion: dict[int | tuple[int, ...], list[int]] = {}
244
+ listAddends: list[int] = getDictionaryAddends4Next(state)[indexLeafAddend]
245
+
246
+ for groupByIndexLeaves, listIndexLeavesIncludedAtPile in groupedBy.items():
247
+ listAddendIndicesIncluded: list[int] = [addendIndex for addendIndex, addend in enumerate(listAddends) if indexLeafAddend + addend in listIndexLeavesIncludedAtPile]
248
+ listAddendIndicesExcluded: list[int] = sorted(set(range(len(listAddends))).difference(set(listAddendIndicesIncluded)))
249
+ dictionaryExclusion[groupByIndexLeaves] = listAddendIndicesExcluded
250
+
251
+ return dictionaryExclusion
252
+
253
+ def getExcludedIndexLeaves(state: EliminationState, pileTarget: int, groupByIndexLeavesAtPiles: tuple[int, ...]) -> dict[int | tuple[int, ...], list[int]]:
254
+ return {indexLeaves: sorted(set(getDictionaryPileToLeaves(state)[pileTarget]).difference(set(listLeaves))) for indexLeaves, listLeaves in _getGroupedBy(state, pileTarget, groupByIndexLeavesAtPiles).items()}
255
+
256
+ if __name__ == '__main__':
257
+ from mapFolding.tests.verify import (
258
+ verifyDictionaryAddends4Next, verifyDictionaryAddends4Prior, verifyDictionaryLeafRanges)
259
+
260
+ state = EliminationState((2,) * 6)
261
+ dictionaryLeafRanges = getDictionaryIndexLeafRanges(state)
262
+ # verifyDictionaryLeafRanges(state, dictionaryLeafRanges)
263
+ dictionaryAddends4Next = getDictionaryAddends4Next(state)
264
+ # verifyDictionaryAddends4Next(state, dictionaryAddends4Next)
265
+ dictionaryAddends4Prior = getDictionaryAddends4Prior(state)
266
+ verifyDictionaryAddends4Prior(state, dictionaryAddends4Prior)
267
+
268
+ # dictionaryPileToLeaves = getDictionaryPileToLeaves(state)
269
+ colorReset = '\33[0m'
270
+ color = '\33[91m'
271
+
272
+ # print(dictionaryPileToLeaves[31])
273
+
274
+ # for indexLeaf in [*dictionaryPileToLeaves[31]]:
275
+ # print(f"{indexLeaf:2}: {distanceFromPowerOf2(indexLeaf - 0b000011).bit_count()} ", end='')
276
+ # for d in range(state.dimensionsTotal - 1, -1, -1):
277
+ # bit = gmpy2.bit_test(indexLeaf, d)
278
+ # print(f'\33[{3+(d&0)}{int(bit+3)}m', "█▊", colorReset, sep='', end='')
279
+ # print()
280
+