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.
- easyRun/NOTcountingFolds.py +16 -6
- easyRun/__init__.py +1 -0
- easyRun/countFolds.py +12 -5
- easyRun/eliminateFolds.py +60 -0
- mapFolding/__init__.py +2 -1
- mapFolding/_theTypes.py +0 -1
- mapFolding/algorithms/A086345.py +8 -3
- mapFolding/algorithms/__init__.py +1 -1
- mapFolding/algorithms/constraintPropagation.py +184 -0
- mapFolding/algorithms/elimination.py +131 -0
- mapFolding/algorithms/eliminationCount.py +26 -0
- mapFolding/algorithms/eliminationPinned.py +35 -0
- mapFolding/algorithms/iff.py +206 -0
- mapFolding/algorithms/patternFinder.py +280 -0
- mapFolding/algorithms/pinning2Dn.py +345 -0
- mapFolding/algorithms/pinning2DnAnnex.py +43 -0
- mapFolding/basecamp.py +72 -18
- mapFolding/beDRY.py +14 -1
- mapFolding/dataBaskets.py +56 -0
- mapFolding/someAssemblyRequired/transformationTools.py +1 -1
- mapFolding/tests/conftest.py +1 -1
- mapFolding/tests/test_computations.py +17 -26
- mapFolding/tests/verify.py +323 -0
- {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/METADATA +6 -3
- {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/RECORD +29 -24
- easyRun/A000682.py +0 -25
- easyRun/A005316.py +0 -20
- mapFolding/algorithms/A000136constraintPropagation.py +0 -95
- mapFolding/algorithms/A000136elimination.py +0 -163
- mapFolding/algorithms/A000136eliminationParallel.py +0 -77
- {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/WHEEL +0 -0
- {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.17.0.dist-info → mapfolding-0.17.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
|