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
easyRun/NOTcountingFolds.py
CHANGED
|
@@ -22,15 +22,26 @@ if __name__ == '__main__':
|
|
|
22
22
|
flow: str | None = None
|
|
23
23
|
|
|
24
24
|
oeisID = 'A007822'
|
|
25
|
+
oeisID = 'A000136'
|
|
25
26
|
|
|
26
27
|
flow = 'algorithm'
|
|
27
|
-
flow = 'asynchronous'
|
|
28
|
-
flow = 'theorem2Trimmed'
|
|
29
|
-
flow = 'theorem2Numba'
|
|
30
28
|
flow = 'theorem2'
|
|
29
|
+
flow = 'eliminationParallel'
|
|
30
|
+
flow = 'elimination_combi'
|
|
31
|
+
flow = 'constraintPropagation'
|
|
32
|
+
flow = 'elimination'
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
sys.stdout.write(f"\033[{30+int(oeisID,11)%8};{40+int(oeisID,12)%8}m{oeisID} ")
|
|
35
|
+
sys.stdout.write(f"\033[{31+int(flow,35)%7};{41+int(flow,36)%7}m{flow}")
|
|
36
|
+
sys.stdout.write("\033[0m\n")
|
|
37
|
+
|
|
38
|
+
nList: list[int] = []
|
|
39
|
+
nList.extend(range(7, 11))
|
|
40
|
+
# nList.extend(range(9, 13))
|
|
41
|
+
# nList.extend(range(11, 15))
|
|
42
|
+
# nList.extend(range(13, 17))
|
|
43
|
+
|
|
44
|
+
for n in dict.fromkeys(nList):
|
|
34
45
|
|
|
35
46
|
timeStart = time.perf_counter()
|
|
36
47
|
countTotal = NOTcountingFolds(oeisID, n, flow, CPUlimit)
|
|
@@ -41,4 +52,3 @@ r"""
|
|
|
41
52
|
deactivate && C:\apps\mapFolding\.vtail\Scripts\activate.bat && title good && cls
|
|
42
53
|
title running && start "working" /B /HIGH /wait py -X faulthandler=0 -X tracemalloc=0 -X frozen_modules=on easyRun\NOTcountingFolds.py & title I'm done
|
|
43
54
|
"""
|
|
44
|
-
|
easyRun/__init__.py
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Instead of learning to make a Python UI, I made these modules."""
|
easyRun/countFolds.py
CHANGED
|
@@ -14,6 +14,7 @@ if __name__ == '__main__':
|
|
|
14
14
|
f"\033[{(not match)*91}m"
|
|
15
15
|
f"{n}\t"
|
|
16
16
|
f"{foldsTotal}\t"
|
|
17
|
+
f"{dictionaryOEISMapFolding[oeisID]['valuesKnown'][n]}\t"
|
|
17
18
|
f"{time.perf_counter() - timeStart:.2f}\t"
|
|
18
19
|
"\033[0m\n"
|
|
19
20
|
)
|
|
@@ -27,17 +28,23 @@ if __name__ == '__main__':
|
|
|
27
28
|
flow = 'numba'
|
|
28
29
|
flow = 'theorem2'
|
|
29
30
|
flow = 'theorem2Numba'
|
|
30
|
-
flow: str | None = 'theorem2Trimmed'
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
oeisID: str = '
|
|
32
|
+
oeisID: str = 'A195646'
|
|
33
|
+
oeisID: str = 'A001418'
|
|
34
34
|
oeisID: str = 'A000136'
|
|
35
|
-
|
|
35
|
+
oeisID: str = 'A001416'
|
|
36
|
+
oeisID: str = 'A001415'
|
|
37
|
+
oeisID: str = 'A001417'
|
|
38
|
+
|
|
39
|
+
sys.stdout.write(f"\033[{30+int(oeisID,11)%8};{40+int(oeisID,12)%8}m{oeisID} ")
|
|
40
|
+
sys.stdout.write(f"\033[{31+int(flow,35)%7};{41+int(flow,36)%7}m{flow}")
|
|
41
|
+
sys.stdout.write("\033[0m\n")
|
|
42
|
+
|
|
43
|
+
for n in range(6,7):
|
|
36
44
|
|
|
37
45
|
mapShape: tuple[int, ...] = dictionaryOEISMapFolding[oeisID]['getMapShape'](n)
|
|
38
46
|
|
|
39
47
|
timeStart = time.perf_counter()
|
|
40
|
-
# foldsTotal: int = countFolds(listDimensions=None, pathLikeWriteFoldsTotal=None, computationDivisions=None, CPUlimit=None, mapShape=(2, 3), flow='theorem2Trimmed')
|
|
41
48
|
foldsTotal: int = countFolds(listDimensions=listDimensions
|
|
42
49
|
, pathLikeWriteFoldsTotal=pathLikeWriteFoldsTotal
|
|
43
50
|
, computationDivisions=computationDivisions
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ruff: noqa
|
|
2
|
+
# pyright: basic
|
|
3
|
+
from mapFolding import dictionaryOEISMapFolding, eliminateFolds
|
|
4
|
+
from os import PathLike
|
|
5
|
+
from pathlib import PurePath
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
if __name__ == '__main__':
|
|
10
|
+
def _write() -> None:
|
|
11
|
+
sys.stdout.write(
|
|
12
|
+
f"{(match:=foldsTotal == dictionaryOEISMapFolding[oeisID]['valuesKnown'][n])}\t"
|
|
13
|
+
f"\033[{(not match)*91}m"
|
|
14
|
+
f"{n}\t"
|
|
15
|
+
# f"{mapShape}\t"
|
|
16
|
+
f"{foldsTotal}\t"
|
|
17
|
+
f"{dictionaryOEISMapFolding[oeisID]['valuesKnown'][n]}\t"
|
|
18
|
+
f"{time.perf_counter() - timeStart:.2f}\t"
|
|
19
|
+
"\033[0m\n"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
pathLikeWriteFoldsTotal: PathLike[str] | PurePath | None = None
|
|
23
|
+
oeisID: str = ''
|
|
24
|
+
flow: str = ''
|
|
25
|
+
CPUlimit: bool | float | int | None = -2
|
|
26
|
+
|
|
27
|
+
flow = 'elimination'
|
|
28
|
+
flow = 'constraintPropagation'
|
|
29
|
+
|
|
30
|
+
oeisID: str = 'A000136'
|
|
31
|
+
oeisID: str = 'A001416'
|
|
32
|
+
oeisID: str = 'A195646'
|
|
33
|
+
oeisID: str = 'A001415'
|
|
34
|
+
oeisID: str = 'A001418'
|
|
35
|
+
oeisID: str = 'A001417'
|
|
36
|
+
|
|
37
|
+
sys.stdout.write(f"\033[{30+int(oeisID,11)%8};{40+int(oeisID,12)%8}m{oeisID} ")
|
|
38
|
+
sys.stdout.write(f"\033[{31+int(flow,35)%7};{41+int(flow,36)%7}m{flow}")
|
|
39
|
+
sys.stdout.write("\033[0m\n")
|
|
40
|
+
|
|
41
|
+
for n in range(7,8):
|
|
42
|
+
|
|
43
|
+
mapShape: tuple[int, ...] = dictionaryOEISMapFolding[oeisID]['getMapShape'](n)
|
|
44
|
+
|
|
45
|
+
timeStart = time.perf_counter()
|
|
46
|
+
foldsTotal: int = eliminateFolds(
|
|
47
|
+
mapShape=mapShape
|
|
48
|
+
, pathLikeWriteFoldsTotal=pathLikeWriteFoldsTotal
|
|
49
|
+
, CPUlimit=CPUlimit
|
|
50
|
+
, flow=flow)
|
|
51
|
+
|
|
52
|
+
_write()
|
|
53
|
+
|
|
54
|
+
r"""
|
|
55
|
+
deactivate && C:\apps\mapFolding\.vtail\Scripts\activate.bat && title good && cls
|
|
56
|
+
|
|
57
|
+
title running && start "working" /B /HIGH /wait py -X faulthandler=0 -X tracemalloc=0 -X frozen_modules=on easyRun\eliminateFolds.py & title I'm done
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# maps of 3 x 3 ... x 3, divisible by leavesTotal * 2^dimensionsTotal * factorial(dimensionsTotal)
|
mapFolding/__init__.py
CHANGED
|
@@ -24,6 +24,7 @@ from mapFolding._theTypes import (
|
|
|
24
24
|
from mapFolding._theSSOT import packageSettings as packageSettings
|
|
25
25
|
|
|
26
26
|
from mapFolding.beDRY import (
|
|
27
|
+
exclude as exclude,
|
|
27
28
|
getConnectionGraph as getConnectionGraph,
|
|
28
29
|
getLeavesTotal as getLeavesTotal,
|
|
29
30
|
getTaskDivisions as getTaskDivisions,
|
|
@@ -38,7 +39,7 @@ from mapFolding.filesystemToolkit import (
|
|
|
38
39
|
saveFoldsTotal as saveFoldsTotal,
|
|
39
40
|
saveFoldsTotalFAILearly as saveFoldsTotalFAILearly)
|
|
40
41
|
|
|
41
|
-
from mapFolding.basecamp import countFolds as countFolds
|
|
42
|
+
from mapFolding.basecamp import countFolds as countFolds, eliminateFolds as eliminateFolds
|
|
42
43
|
|
|
43
44
|
from mapFolding.oeis import (
|
|
44
45
|
dictionaryOEIS as dictionaryOEIS,
|
mapFolding/_theTypes.py
CHANGED
mapFolding/algorithms/A086345.py
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
"""Directly based on code Chai Wah Wu, https://oeis.org/wiki/User:Chai_Wah_Wu, posted on OEIS.
|
|
1
|
+
"""Directly based on code by Chai Wah Wu, https://oeis.org/wiki/User:Chai_Wah_Wu, posted on OEIS.
|
|
2
|
+
|
|
3
|
+
See Also
|
|
4
|
+
--------
|
|
5
|
+
mapFolding/reference/A086345Wu.py
|
|
6
|
+
"""
|
|
2
7
|
from fractions import Fraction
|
|
3
8
|
from functools import cache
|
|
4
9
|
from itertools import combinations
|
|
@@ -32,7 +37,7 @@ def _blender(n: int) -> int:
|
|
|
32
37
|
nummaNumma += _goRight(integer, copies)
|
|
33
38
|
denominator *= _deFactorial(integer, copies)
|
|
34
39
|
numerator: int = 3 ** (numbinations + nummaNumma)
|
|
35
|
-
sumReBletionary += Fraction(numerator, denominator)
|
|
40
|
+
sumReBletionary += Fraction(numerator, denominator) # pyright: ignore[reportAssignmentType]
|
|
36
41
|
return sumReBletionary
|
|
37
42
|
|
|
38
43
|
@cache
|
|
@@ -70,6 +75,6 @@ def A086345(n: int) -> int:
|
|
|
70
75
|
else:
|
|
71
76
|
aOFn: int = 0
|
|
72
77
|
for aDivisor in divisors(n, generator=True):
|
|
73
|
-
aOFn += mobius(aDivisor) * _recurser(n//aDivisor)
|
|
78
|
+
aOFn += mobius(aDivisor) * _recurser(n//aDivisor) # pyright: ignore[reportAssignmentType]
|
|
74
79
|
aOFn //= n
|
|
75
80
|
return aOFn
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"""Hand-made algorithms."""
|
|
1
|
+
"""Hand-made algorithms and formulas."""
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# ruff: noqa: ERA001
|
|
2
|
+
from concurrent.futures import as_completed, Future, ProcessPoolExecutor
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from itertools import pairwise, product as CartesianProduct
|
|
5
|
+
from mapFolding.algorithms.patternFinder import getDictionaryIndexLeafRanges
|
|
6
|
+
from mapFolding.algorithms.pinning2Dn import pinByFormula, secondOrderLeaves, secondOrderPilings
|
|
7
|
+
from mapFolding.dataBaskets import EliminationState
|
|
8
|
+
from math import factorial, prod
|
|
9
|
+
from more_itertools import iter_index, unique
|
|
10
|
+
from ortools.sat.python import cp_model
|
|
11
|
+
from tqdm import tqdm
|
|
12
|
+
from typing import Final
|
|
13
|
+
|
|
14
|
+
def findValidFoldings(state: EliminationState) -> int:
|
|
15
|
+
model = cp_model.CpModel()
|
|
16
|
+
|
|
17
|
+
listIndicesLeafInPilingsOrder: list[cp_model.IntVar] = [model.NewIntVar(0, state.leavesTotal - 1, f"indexLeafInPile[{pile}]") for pile in range(state.leavesTotal)]
|
|
18
|
+
listPilingsInIndexLeafOrder: list[cp_model.IntVar] = [model.NewIntVar(0, state.leavesTotal - 1, f"pileOfIndexLeaf[{indexLeaf}]") for indexLeaf in range(state.leavesTotal)]
|
|
19
|
+
model.AddInverse(listIndicesLeafInPilingsOrder, listPilingsInIndexLeafOrder)
|
|
20
|
+
|
|
21
|
+
# ------- Leaf domain restrictions from dictionaryLeafRanges -----------------------------
|
|
22
|
+
if (state.dimensionsTotal > 2) and (state.mapShape[0] == 2):
|
|
23
|
+
dictionaryLeafRanges: Final[dict[int, range]] = getDictionaryIndexLeafRanges(state)
|
|
24
|
+
for indexLeaf, rangePilings in dictionaryLeafRanges.items():
|
|
25
|
+
if indexLeaf < 2:
|
|
26
|
+
continue
|
|
27
|
+
model.AddAllowedAssignments([listPilingsInIndexLeafOrder[indexLeaf]], [(pile,) for pile in rangePilings])
|
|
28
|
+
|
|
29
|
+
if state.leavesTotal in [64, 128]:
|
|
30
|
+
from mapFolding.algorithms.patternFinder import getDictionaryAddends4Next # noqa: PLC0415
|
|
31
|
+
dictionaryAddends4Next: Final[dict[int, list[int]]] = getDictionaryAddends4Next(state)
|
|
32
|
+
dictionaryNextLeaf: dict[int, list[int]] = {}
|
|
33
|
+
for indexLeaf, listDifferences in dictionaryAddends4Next.items():
|
|
34
|
+
listAllowedNextLeaves: list[int] = []
|
|
35
|
+
for difference in listDifferences:
|
|
36
|
+
listAllowedNextLeaves.append(indexLeaf + difference) # noqa: PERF401
|
|
37
|
+
dictionaryNextLeaf[indexLeaf] = listAllowedNextLeaves
|
|
38
|
+
|
|
39
|
+
# ------- Constraints from dictionaryNextLeaf -----------------------------
|
|
40
|
+
for indexLeaf, listAllowedNextLeaves in dictionaryNextLeaf.items():
|
|
41
|
+
if not listAllowedNextLeaves:
|
|
42
|
+
continue
|
|
43
|
+
for pile in range(state.leavesTotal - 1):
|
|
44
|
+
currentLeafAtThisPile: cp_model.IntVar = listIndicesLeafInPilingsOrder[pile]
|
|
45
|
+
nextLeafAtNextPile: cp_model.IntVar = listIndicesLeafInPilingsOrder[pile + 1]
|
|
46
|
+
|
|
47
|
+
isCurrentLeafEqualToIndexLeaf: cp_model.IntVar = model.NewBoolVar(f"pile{pile}_leaf{indexLeaf}")
|
|
48
|
+
model.Add(currentLeafAtThisPile == indexLeaf).OnlyEnforceIf(isCurrentLeafEqualToIndexLeaf)
|
|
49
|
+
model.Add(currentLeafAtThisPile != indexLeaf).OnlyEnforceIf(isCurrentLeafEqualToIndexLeaf.Not())
|
|
50
|
+
|
|
51
|
+
model.AddAllowedAssignments([nextLeafAtNextPile], [(leaf,) for leaf in listAllowedNextLeaves]).OnlyEnforceIf(isCurrentLeafEqualToIndexLeaf)
|
|
52
|
+
|
|
53
|
+
# ------- Manual concurrency -----------------------------
|
|
54
|
+
for pile, indexLeaf in state.pinnedLeaves.items():
|
|
55
|
+
model.Add(listIndicesLeafInPilingsOrder[pile] == indexLeaf)
|
|
56
|
+
|
|
57
|
+
# ------- Lunnon Theorem 2(a): foldsTotal is divisible by leavesTotal; fix in pile at 0, indexLeaf at 0 -----------------------------
|
|
58
|
+
model.Add(listIndicesLeafInPilingsOrder[0] == 0)
|
|
59
|
+
|
|
60
|
+
# ------- Lunnon Theorem 4: "G(p^d) is divisible by d!p^d." ---------------
|
|
61
|
+
for listIndicesSameMagnitude in [list(iter_index(state.mapShape, magnitude)) for magnitude in unique(state.mapShape)]:
|
|
62
|
+
if len(listIndicesSameMagnitude) > 1:
|
|
63
|
+
state.subsetsTheorem4 *= factorial(len(listIndicesSameMagnitude))
|
|
64
|
+
for dimensionAlpha, dimensionBeta in pairwise(listIndicesSameMagnitude):
|
|
65
|
+
k, r = (prod(state.mapShape[0:dimension]) for dimension in (dimensionAlpha, dimensionBeta))
|
|
66
|
+
model.Add(listPilingsInIndexLeafOrder[k] < listPilingsInIndexLeafOrder[r])
|
|
67
|
+
|
|
68
|
+
# ------- Lunnon Theorem 2(b): "If some [magnitude in state.mapShape] > 2, [foldsTotal] is divisible by 2 * [leavesTotal]." -----------------------------
|
|
69
|
+
if state.subsetsTheorem4 == 1:
|
|
70
|
+
for aDimension in range(state.dimensionsTotal - 1, -1, -1):
|
|
71
|
+
if state.mapShape[aDimension] > 2:
|
|
72
|
+
state.subsetsTheorem2 = 2
|
|
73
|
+
indexLeafOrigin下_aDimension: int = prod(state.mapShape[0:aDimension])
|
|
74
|
+
model.Add(listPilingsInIndexLeafOrder[indexLeafOrigin下_aDimension] < listPilingsInIndexLeafOrder[2 * indexLeafOrigin下_aDimension])
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
# ------- Forbidden inequalities -----------------------------
|
|
78
|
+
def addLessThan(comparatorLeft: int, comparatorRight: int) -> cp_model.IntVar:
|
|
79
|
+
ruleΩ: cp_model.IntVar = model.NewBoolVar(f"this_{comparatorLeft}_lessThan_{comparatorRight}")
|
|
80
|
+
model.Add(listPilingsInIndexLeafOrder[comparatorLeft] < listPilingsInIndexLeafOrder[comparatorRight]).OnlyEnforceIf(ruleΩ)
|
|
81
|
+
model.Add(listPilingsInIndexLeafOrder[comparatorLeft] >= listPilingsInIndexLeafOrder[comparatorRight]).OnlyEnforceIf(ruleΩ.Not())
|
|
82
|
+
return ruleΩ
|
|
83
|
+
|
|
84
|
+
def addForbiddenInequalityCycle(k: int, r: int, k1: int, r1: int) -> None:
|
|
85
|
+
k__小于_r: cp_model.IntVar = addLessThan(k, r) # 小, xiǎo: small, less; as in 李小龍, Lǐ Xiǎolóng, Lǐ little dragon, aka Bruce Lee
|
|
86
|
+
r1_小于_k: cp_model.IntVar = addLessThan(r1, k)
|
|
87
|
+
k1_小于_r1: cp_model.IntVar = addLessThan(k1, r1)
|
|
88
|
+
model.AddBoolOr([k1_小于_r1.Not(), r1_小于_k.Not(), k__小于_r.Not()]) # [k+1 < r+1 < k < r]
|
|
89
|
+
|
|
90
|
+
r__小于_k1: cp_model.IntVar = addLessThan(r, k1)
|
|
91
|
+
model.AddBoolOr([r1_小于_k.Not(), k__小于_r.Not(), r__小于_k1.Not()]) # [r+1 < k < r < k+1]
|
|
92
|
+
|
|
93
|
+
model.AddBoolOr([k__小于_r.Not(), r__小于_k1.Not(), k1_小于_r1.Not()]) # [k < r < k+1 < r+1]
|
|
94
|
+
|
|
95
|
+
k__小于_r1: cp_model.IntVar = addLessThan(k, r1)
|
|
96
|
+
r1_小于_k1: cp_model.IntVar = addLessThan(r1, k1)
|
|
97
|
+
k1_小于_r: cp_model.IntVar = addLessThan(k1, r)
|
|
98
|
+
model.AddBoolOr([k__小于_r1.Not(), r1_小于_k1.Not(), k1_小于_r.Not()]) # [k < r+1 < k+1 < r]
|
|
99
|
+
|
|
100
|
+
def indexLeaf2IndicesCartesian(indexLeaf: int) -> tuple[int, ...]:
|
|
101
|
+
return tuple((indexLeaf // prod(state.mapShape[0:dimension])) % state.mapShape[dimension] for dimension in range(state.dimensionsTotal))
|
|
102
|
+
|
|
103
|
+
def indexLeafNextCrease(indexLeaf: int, dimension: int) -> int | None:
|
|
104
|
+
indexLeafNext: int | None = None
|
|
105
|
+
if indexLeaf2IndicesCartesian(indexLeaf)[dimension] + 1 < state.mapShape[dimension]:
|
|
106
|
+
indexLeafNext = indexLeaf + prod(state.mapShape[0:dimension])
|
|
107
|
+
return indexLeafNext
|
|
108
|
+
|
|
109
|
+
for k, r in CartesianProduct(range(state.leavesTotal-1), range(1, state.leavesTotal-1)):
|
|
110
|
+
if k == r:
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
k下_indicesCartesian: tuple[int, ...] = indexLeaf2IndicesCartesian(k) # 下, xià: below, subscript
|
|
114
|
+
r下_indicesCartesian: tuple[int, ...] = indexLeaf2IndicesCartesian(r)
|
|
115
|
+
|
|
116
|
+
for aDimension in range(state.dimensionsTotal):
|
|
117
|
+
k1下_aDimension: int | None = indexLeafNextCrease(k, aDimension)
|
|
118
|
+
r1下_aDimension: int | None = indexLeafNextCrease(r, aDimension)
|
|
119
|
+
|
|
120
|
+
if k1下_aDimension and r1下_aDimension and ((k下_indicesCartesian[aDimension] - r下_indicesCartesian[aDimension]) % 2 == 0):
|
|
121
|
+
addForbiddenInequalityCycle(k, r, k1下_aDimension, r1下_aDimension)
|
|
122
|
+
|
|
123
|
+
# ------- Solver -----------------------------
|
|
124
|
+
solver = cp_model.CpSolver()
|
|
125
|
+
solver.parameters.enumerate_all_solutions = True
|
|
126
|
+
|
|
127
|
+
solver.parameters.log_search_progress = False
|
|
128
|
+
|
|
129
|
+
class FoldingCollector(cp_model.CpSolverSolutionCallback):
|
|
130
|
+
def __init__(self, _listOfIndicesLeafInPilingsOrder: list[cp_model.IntVar]) -> None:
|
|
131
|
+
super().__init__()
|
|
132
|
+
self._listOfIndicesLeafInPilingsOrder: list[cp_model.IntVar] = _listOfIndicesLeafInPilingsOrder
|
|
133
|
+
self.listFoldings: list[list[int]] = []
|
|
134
|
+
|
|
135
|
+
def OnSolutionCallback(self) -> None:
|
|
136
|
+
self.listFoldings.append([self.Value(indexLeaf) for indexLeaf in self._listOfIndicesLeafInPilingsOrder]) # pyright: ignore[reportUnknownMemberType]
|
|
137
|
+
|
|
138
|
+
foldingCollector = FoldingCollector(listIndicesLeafInPilingsOrder)
|
|
139
|
+
solver.Solve(model, foldingCollector)
|
|
140
|
+
|
|
141
|
+
# if not foldingCollector.listFoldings:
|
|
142
|
+
# print("\n",state.pinnedLeaves)
|
|
143
|
+
# if foldingCollector.listFoldings:
|
|
144
|
+
# print(*foldingCollector.listFoldings, sep="\n")
|
|
145
|
+
|
|
146
|
+
return len(foldingCollector.listFoldings) * state.subsetsTheorem2 * state.subsetsTheorem4
|
|
147
|
+
|
|
148
|
+
def doTheNeedful(state: EliminationState, workersMaximum: int) -> EliminationState:
|
|
149
|
+
"""Find the quantity of valid foldings for a given map."""
|
|
150
|
+
# state = pinByFormula(state)
|
|
151
|
+
# state = secondOrderLeaves(state)
|
|
152
|
+
# state = secondOrderPilings(state)
|
|
153
|
+
|
|
154
|
+
if state.listPinnedLeaves:
|
|
155
|
+
|
|
156
|
+
with ProcessPoolExecutor(workersMaximum) as concurrencyManager:
|
|
157
|
+
listClaimTickets: list[Future[int]] = []
|
|
158
|
+
|
|
159
|
+
listPinnedLeavesCopy: list[dict[int, int]] = deepcopy(state.listPinnedLeaves)
|
|
160
|
+
state.listPinnedLeaves = []
|
|
161
|
+
for pinnedLeaves in listPinnedLeavesCopy:
|
|
162
|
+
stateCopy: EliminationState = deepcopy(state)
|
|
163
|
+
stateCopy.pinnedLeaves = pinnedLeaves
|
|
164
|
+
listClaimTickets.append(concurrencyManager.submit(findValidFoldings, stateCopy))
|
|
165
|
+
|
|
166
|
+
for claimTicket in tqdm(as_completed(listClaimTickets), total=len(listClaimTickets), disable=False):
|
|
167
|
+
state.groupsOfFolds += claimTicket.result()
|
|
168
|
+
|
|
169
|
+
elif workersMaximum > 1:
|
|
170
|
+
pile = 2
|
|
171
|
+
with ProcessPoolExecutor(workersMaximum) as concurrencyManager:
|
|
172
|
+
listClaimTickets: list[Future[int]] = []
|
|
173
|
+
for indicesLeaf in range(1, state.leavesTotal):
|
|
174
|
+
stateCopy: EliminationState = deepcopy(state)
|
|
175
|
+
stateCopy.pinnedLeaves = {pile: indicesLeaf}
|
|
176
|
+
listClaimTickets.append(concurrencyManager.submit(findValidFoldings, stateCopy))
|
|
177
|
+
|
|
178
|
+
for claimTicket in listClaimTickets:
|
|
179
|
+
state.groupsOfFolds += claimTicket.result()
|
|
180
|
+
|
|
181
|
+
else:
|
|
182
|
+
state.groupsOfFolds = findValidFoldings(deepcopy(state))
|
|
183
|
+
|
|
184
|
+
return state
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from cytoolz.functoolz import curry as syntacticCurry
|
|
2
|
+
from cytoolz.itertoolz import groupby as toolz_groupby
|
|
3
|
+
from itertools import pairwise, repeat
|
|
4
|
+
from mapFolding.algorithms.eliminationCount import count, permutands
|
|
5
|
+
from mapFolding.algorithms.iff import productOfDimensions
|
|
6
|
+
from mapFolding.algorithms.pinning2Dn import pinByFormula
|
|
7
|
+
from mapFolding.dataBaskets import EliminationState
|
|
8
|
+
from math import factorial
|
|
9
|
+
from more_itertools import flatten, iter_index, unique
|
|
10
|
+
from typing import Any, TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
@syntacticCurry
|
|
16
|
+
def _excludeLeafAtColumn(sequencePinnedLeaves: list[dict[int, int]], leaf: int, column: int, leavesTotal: int) -> list[dict[int, int]]:
|
|
17
|
+
listPinnedLeaves: list[dict[int, int]] = []
|
|
18
|
+
for pinnedLeaves in sequencePinnedLeaves:
|
|
19
|
+
leafAtColumn: int | None = pinnedLeaves.get(column)
|
|
20
|
+
if leaf == leafAtColumn:
|
|
21
|
+
continue # Exclude `leaf` previously fixed at `column`.
|
|
22
|
+
if (leafAtColumn is not None) or (leaf in pinnedLeaves.values()): # `column` is occupied, which excludes `leaf`.
|
|
23
|
+
listPinnedLeaves.append(pinnedLeaves) # Or `leaf` is pinned, but not at `column`.
|
|
24
|
+
continue
|
|
25
|
+
deconstructedPinnedLeaves: dict[int, dict[int, int]] = deconstructPinnedLeaves(pinnedLeaves, column, leavesTotal)
|
|
26
|
+
deconstructedPinnedLeaves.pop(leaf) # Exclude dictionary with `leaf` fixed at `column`.
|
|
27
|
+
listPinnedLeaves.extend(deconstructedPinnedLeaves.values())
|
|
28
|
+
return listPinnedLeaves
|
|
29
|
+
|
|
30
|
+
def pinLeafAtColumn(sequencePinnedLeaves: list[dict[int, int]], leaf: int, column: int, leavesTotal: int) -> list[dict[int, int]]:
|
|
31
|
+
listPinnedLeaves: list[dict[int, int]] = []
|
|
32
|
+
for pinnedLeaves in sequencePinnedLeaves:
|
|
33
|
+
leafAtColumn: int | None = pinnedLeaves.get(column)
|
|
34
|
+
if leaf == leafAtColumn:
|
|
35
|
+
listPinnedLeaves.append(pinnedLeaves) # That was easy.
|
|
36
|
+
continue
|
|
37
|
+
if leafAtColumn is not None: # `column` is occupied, but not by `leaf`, so exclude it.
|
|
38
|
+
continue
|
|
39
|
+
listPinnedLeaves.append(deconstructPinnedLeaves(pinnedLeaves, column, leavesTotal).pop(leaf)) # Keep the dictionary with `leaf` fixed at `column`.
|
|
40
|
+
return listPinnedLeaves
|
|
41
|
+
|
|
42
|
+
@syntacticCurry
|
|
43
|
+
def _isPinnedAtColumn(pinnedLeaves: dict[int, int], leaf: int, column: int) -> bool:
|
|
44
|
+
return leaf == pinnedLeaves.get(column)
|
|
45
|
+
|
|
46
|
+
def _segregatePinnedAtColumn(listPinnedLeaves: list[dict[int, int]], leaf: int, column: int) -> tuple[list[dict[int, int]], list[dict[int, int]]]:
|
|
47
|
+
isPinned: Callable[[dict[int, int]], bool] = _isPinnedAtColumn(leaf=leaf, column=column)
|
|
48
|
+
grouped: dict[bool, list[dict[int, int]]] = toolz_groupby(isPinned, listPinnedLeaves)
|
|
49
|
+
return (grouped.get(False, []), grouped.get(True, []))
|
|
50
|
+
|
|
51
|
+
@syntacticCurry
|
|
52
|
+
def atColumnPinLeaf(pinnedLeaves: dict[int, int], column: int, leaf: int) -> dict[int, int]:
|
|
53
|
+
dictionaryPinnedLeaves: dict[int, int] = dict(pinnedLeaves)
|
|
54
|
+
dictionaryPinnedLeaves[column] = leaf
|
|
55
|
+
return dictionaryPinnedLeaves
|
|
56
|
+
|
|
57
|
+
def deconstructPinnedLeaves(pinnedLeaves: dict[int, int], column: int, leavesTotal: int) -> dict[int, dict[int, int]]:
|
|
58
|
+
"""Replace `pinnedLeaves`, which doesn't pin a leaf at `column`, with the equivalent group of dictionaries, which each pin a distinct leaf at `column`.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
pinnedLeaves : dict[int, int]
|
|
63
|
+
Dictionary to divide and replace.
|
|
64
|
+
column : int
|
|
65
|
+
Column in which to pin a leaf.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
deconstructedPinnedLeaves : dict[int, dict[int, int]]
|
|
70
|
+
Dictionary mapping from `leaf` pinned at `column` to the dictionary with the `leaf` pinned at `column`.
|
|
71
|
+
"""
|
|
72
|
+
leafAtColumn: int | None = pinnedLeaves.get(column)
|
|
73
|
+
if leafAtColumn is not None:
|
|
74
|
+
deconstructedPinnedLeaves: dict[int, dict[int, int]] = {leafAtColumn: pinnedLeaves}
|
|
75
|
+
else:
|
|
76
|
+
pin: Callable[[int], dict[int, int]] = atColumnPinLeaf(pinnedLeaves, column)
|
|
77
|
+
deconstructedPinnedLeaves = {leaf: pin(leaf) for leaf in permutands(pinnedLeaves, leavesTotal)}
|
|
78
|
+
return deconstructedPinnedLeaves
|
|
79
|
+
|
|
80
|
+
def DOTvalues[个](dictionary: dict[Any, 个]) -> list[个]:
|
|
81
|
+
return list(dictionary.values())
|
|
82
|
+
|
|
83
|
+
def deconstructListPinnedLeaves(listPinnedLeaves: list[dict[int, int]], column: int, leavesTotal: int) -> list[dict[int, int]]:
|
|
84
|
+
return list(flatten(map(DOTvalues, map(deconstructPinnedLeaves, listPinnedLeaves, repeat(column), repeat(leavesTotal)))))
|
|
85
|
+
|
|
86
|
+
def _excludeLeafRBeforeLeafK(state: EliminationState, k: int, r: int, columnK: int, listPinnedLeaves: list[dict[int, int]]) -> list[dict[int, int]]:
|
|
87
|
+
listPinnedLeaves = deconstructListPinnedLeaves(listPinnedLeaves, columnK, state.leavesTotal)
|
|
88
|
+
listPinned: list[dict[int, int]] = []
|
|
89
|
+
for column in range(columnK, state.columnLast + 1):
|
|
90
|
+
(listPinnedLeaves, listPinnedAtColumn) = _segregatePinnedAtColumn(listPinnedLeaves, k, column)
|
|
91
|
+
listPinned.extend(listPinnedAtColumn)
|
|
92
|
+
listPinnedLeaves.extend(_excludeLeafAtColumn(listPinned, r, columnK - 1, state.leavesTotal))
|
|
93
|
+
return listPinnedLeaves
|
|
94
|
+
|
|
95
|
+
def excludeLeafRBeforeLeafK(state: EliminationState, k: int, r: int) -> EliminationState:
|
|
96
|
+
for columnK in range(state.columnLast, 0, -1):
|
|
97
|
+
state.listPinnedLeaves = _excludeLeafRBeforeLeafK(state, k, r, columnK, state.listPinnedLeaves)
|
|
98
|
+
return state
|
|
99
|
+
|
|
100
|
+
def theorem4(state: EliminationState) -> EliminationState:
|
|
101
|
+
# ------- Lunnon Theorem 4: "G(p^d) is divisible by d!p^d." ---------------
|
|
102
|
+
for listIndicesSameMagnitude in [list(iter_index(state.mapShape, magnitude)) for magnitude in unique(state.mapShape)]:
|
|
103
|
+
if len(listIndicesSameMagnitude) > 1:
|
|
104
|
+
state.subsetsTheorem4 = factorial(len(listIndicesSameMagnitude))
|
|
105
|
+
for dimensionAlpha, dimensionBeta in pairwise(listIndicesSameMagnitude):
|
|
106
|
+
k, r = (productOfDimensions(state.mapShape, dimension) + 1 for dimension in (dimensionAlpha, dimensionBeta))
|
|
107
|
+
state = excludeLeafRBeforeLeafK(state, k, r)
|
|
108
|
+
return state
|
|
109
|
+
|
|
110
|
+
def theorem2b(state: EliminationState) -> EliminationState:
|
|
111
|
+
# ------- Lunnon Theorem 2(b): "If some pᵢ > 2, G is divisible by 2n." -----------------------------
|
|
112
|
+
if state.subsetsTheorem4 == 1 and max(state.mapShape) > 2:
|
|
113
|
+
state.subsetsTheorem2 = 2
|
|
114
|
+
dimension: int = state.mapShape.index(max(state.mapShape))
|
|
115
|
+
k: int = productOfDimensions(state.mapShape, dimension) + 1
|
|
116
|
+
r: int = 2 * k
|
|
117
|
+
state = excludeLeafRBeforeLeafK(state, k, r)
|
|
118
|
+
|
|
119
|
+
return state
|
|
120
|
+
|
|
121
|
+
def doTheNeedful(state: EliminationState, workersMaximum: int) -> EliminationState: # noqa: ARG001
|
|
122
|
+
"""Count the number of valid foldings for a given number of leaves."""
|
|
123
|
+
# ------- Lunnon Theorem 2(a): foldsTotal is divisible by leavesTotal; Pin leaf1 in column0 and exclude leaf2--leafN at column0 ----------------------------
|
|
124
|
+
state.listPinnedLeaves = [{0: 1}]
|
|
125
|
+
|
|
126
|
+
state = theorem4(state)
|
|
127
|
+
state = theorem2b(state)
|
|
128
|
+
state = pinByFormula(state)
|
|
129
|
+
state = count(state)
|
|
130
|
+
|
|
131
|
+
return state
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from cytoolz.functoolz import memoize
|
|
3
|
+
from itertools import permutations, repeat
|
|
4
|
+
from mapFolding.algorithms.iff import thisLeafFoldingIsValid
|
|
5
|
+
from mapFolding.dataBaskets import EliminationState
|
|
6
|
+
|
|
7
|
+
def _makeFolding(pinnedLeaves: dict[int, int], permutandsPermutation: tuple[int, ...], leavesTotal: int) -> tuple[int, ...]:
|
|
8
|
+
permutand: Iterator[int] = iter(permutandsPermutation)
|
|
9
|
+
return tuple([pinnedLeaves.get(column) or next(permutand) for column in range(leavesTotal)])
|
|
10
|
+
|
|
11
|
+
@memoize
|
|
12
|
+
def setOfLeaves(leavesTotal: int) -> set[int]:
|
|
13
|
+
return set(range(1, leavesTotal + 1))
|
|
14
|
+
|
|
15
|
+
def permutands(pinnedLeaves: dict[int, int], leavesTotal: int) -> tuple[int, ...]:
|
|
16
|
+
return tuple(setOfLeaves(leavesTotal).difference(pinnedLeaves.values()))
|
|
17
|
+
|
|
18
|
+
def permutePermutands(pinnedLeaves: dict[int, int], leavesTotal: int) -> Iterator[tuple[int, ...]]:
|
|
19
|
+
return permutations(permutands(pinnedLeaves, leavesTotal))
|
|
20
|
+
|
|
21
|
+
def countPinnedLeaves(pinnedLeaves: dict[int, int], mapShape: tuple[int, ...], leavesTotal: int) -> int:
|
|
22
|
+
return sum(map(thisLeafFoldingIsValid, map(_makeFolding, repeat(pinnedLeaves), permutePermutands(pinnedLeaves, leavesTotal), repeat(leavesTotal)), repeat(mapShape)))
|
|
23
|
+
|
|
24
|
+
def count(state: EliminationState) -> EliminationState:
|
|
25
|
+
state.groupsOfFolds += sum(map(countPinnedLeaves, state.listPinnedLeaves, repeat(state.mapShape), repeat(state.leavesTotal)))
|
|
26
|
+
return state
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from concurrent.futures import as_completed, Future, ProcessPoolExecutor
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from mapFolding.algorithms.eliminationCount import count
|
|
4
|
+
from mapFolding.algorithms.pinning2Dn import pinByFormula
|
|
5
|
+
from mapFolding.dataBaskets import EliminationState
|
|
6
|
+
from math import e, factorial
|
|
7
|
+
from more_itertools import chunked_even
|
|
8
|
+
from tqdm import tqdm
|
|
9
|
+
|
|
10
|
+
def doTheNeedful(state: EliminationState, workersMaximum: int) -> EliminationState:
|
|
11
|
+
"""Find the quantity of valid foldings for a given map."""
|
|
12
|
+
state = pinByFormula(state)
|
|
13
|
+
|
|
14
|
+
groupsOfFolds:int = 0
|
|
15
|
+
|
|
16
|
+
with ProcessPoolExecutor(workersMaximum) as concurrencyManager:
|
|
17
|
+
listClaimTickets: list[Future[EliminationState]] = []
|
|
18
|
+
|
|
19
|
+
listPinnedLeavesCopy: list[dict[int, int]] = deepcopy(state.listPinnedLeaves)
|
|
20
|
+
state.listPinnedLeaves = []
|
|
21
|
+
|
|
22
|
+
# lengthChunk:int = max(1, int(len(listPinnedLeavesCopy) / (e * workersMaximum)))
|
|
23
|
+
lengthChunk:int = 5
|
|
24
|
+
|
|
25
|
+
for listPinnedLeaves in chunked_even(listPinnedLeavesCopy, lengthChunk):
|
|
26
|
+
stateCopy: EliminationState = deepcopy(state)
|
|
27
|
+
stateCopy.listPinnedLeaves = listPinnedLeaves
|
|
28
|
+
listClaimTickets.append(concurrencyManager.submit(count, stateCopy))
|
|
29
|
+
|
|
30
|
+
for claimTicket in tqdm(as_completed(listClaimTickets), total=len(listClaimTickets), disable=False):
|
|
31
|
+
groupsOfFolds += claimTicket.result().groupsOfFolds
|
|
32
|
+
|
|
33
|
+
state.subsetsTheorem4 = factorial(state.dimensionsTotal)
|
|
34
|
+
|
|
35
|
+
return state
|