mapFolding 0.2.1__py3-none-any.whl → 0.2.3__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.
- mapFolding/__init__.py +2 -2
- mapFolding/babbage.py +2 -2
- mapFolding/beDRY.py +60 -40
- mapFolding/lovelace.py +194 -122
- mapFolding/oeis.py +37 -35
- mapFolding/reference/flattened.py +376 -0
- mapFolding/reference/hunterNumba.py +44 -44
- mapFolding/reference/lunnan.py +5 -5
- mapFolding/reference/lunnanNumpy.py +4 -4
- mapFolding/reference/lunnanWhile.py +5 -5
- mapFolding/reference/rotatedEntryPoint.py +68 -68
- mapFolding/reference/total_countPlus1vsPlusN.py +211 -0
- mapFolding/startHere.py +12 -12
- mapFolding/theSSOT.py +8 -3
- {mapFolding-0.2.1.dist-info → mapFolding-0.2.3.dist-info}/METADATA +4 -4
- mapFolding-0.2.3.dist-info/RECORD +30 -0
- tests/__init__.py +1 -1
- tests/conftest.py +111 -35
- tests/pythons_idiotic_namespace.py +1 -0
- tests/test_oeis.py +25 -26
- tests/test_other.py +135 -5
- tests/test_tasks.py +11 -1
- mapFolding/importPackages.py +0 -5
- mapFolding-0.2.1.dist-info/RECORD +0 -28
- {mapFolding-0.2.1.dist-info → mapFolding-0.2.3.dist-info}/WHEEL +0 -0
- {mapFolding-0.2.1.dist-info → mapFolding-0.2.3.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.2.1.dist-info → mapFolding-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -12,7 +12,7 @@ def foldings(p: Sequence[int]) -> int:
|
|
|
12
12
|
p: An array of integers representing the dimensions of the map.
|
|
13
13
|
|
|
14
14
|
Returns:
|
|
15
|
-
|
|
15
|
+
G: The number of distinct foldings for the given map dimensions.
|
|
16
16
|
|
|
17
17
|
NOTE If there are fewer than two dimensions, any dimensions are not positive, or any dimensions are not integers, the output will be unreliable.
|
|
18
18
|
"""
|
|
@@ -29,7 +29,7 @@ def foldings(p: Sequence[int]) -> int:
|
|
|
29
29
|
B = [0] * (n + 1)
|
|
30
30
|
count = [0] * (n + 1)
|
|
31
31
|
gapter = [0] * (n + 1)
|
|
32
|
-
gap = [0] * (n * n + 1)
|
|
32
|
+
gap = [0] * (n * n + 1)
|
|
33
33
|
|
|
34
34
|
# B[m] is the leaf below leaf m in the current folding,
|
|
35
35
|
# A[m] the leaf above. count[m] is the no. of sections in which
|
|
@@ -64,13 +64,13 @@ def foldings(p: Sequence[int]) -> int:
|
|
|
64
64
|
# P[i] = p[1] x ... x p[i], C[i][m] = i-th co-ordinate of leaf m,
|
|
65
65
|
# D[i][l][m] = leaf connected to m in section i when inserting l;
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
G: int = 0
|
|
68
68
|
l: int = 1
|
|
69
69
|
|
|
70
70
|
# kick off with null folding
|
|
71
71
|
while l > 0:
|
|
72
72
|
if l > n:
|
|
73
|
-
|
|
73
|
+
G = G + 1
|
|
74
74
|
else:
|
|
75
75
|
dd: int = 0
|
|
76
76
|
gg: int = gapter[l - 1]
|
|
@@ -118,4 +118,4 @@ def foldings(p: Sequence[int]) -> int:
|
|
|
118
118
|
A[B[l]] = l
|
|
119
119
|
gapter[l] = g
|
|
120
120
|
l = l + 1
|
|
121
|
-
return
|
|
121
|
+
return G
|
|
@@ -21,11 +21,11 @@ gapter = gapRangeStart = 3
|
|
|
21
21
|
|
|
22
22
|
# Indices of array `my`, which holds dynamic, small, unsigned, integer values.
|
|
23
23
|
tricky = [
|
|
24
|
-
(
|
|
25
|
-
(
|
|
24
|
+
(leaf1ndex := 0),
|
|
25
|
+
(gap1ndex := 1),
|
|
26
26
|
(unconstrainedLeaf := 2),
|
|
27
27
|
(gap1ndexCeiling := 3),
|
|
28
|
-
(
|
|
28
|
+
(leafConnectee := 4),
|
|
29
29
|
(taskIndex := 5),
|
|
30
30
|
(dimension1ndex := 6),
|
|
31
31
|
(foldingsSubtotal := 7),
|
|
@@ -76,7 +76,7 @@ def countFoldings(TEMPLATEtrack: NDArray,
|
|
|
76
76
|
):
|
|
77
77
|
|
|
78
78
|
TEMPLATEmy = numpy.zeros(COUNTindicesDynamic, dtype=numpy.int64)
|
|
79
|
-
TEMPLATEmy[
|
|
79
|
+
TEMPLATEmy[leaf1ndex] = 1
|
|
80
80
|
|
|
81
81
|
taskDivisions = 0
|
|
82
82
|
# taskDivisions = the[leavesTotal]
|
|
@@ -87,61 +87,61 @@ def countFoldings(TEMPLATEtrack: NDArray,
|
|
|
87
87
|
my: NDArray) -> tuple[NDArray, NDArray, NDArray]:
|
|
88
88
|
foldingsTotal = 0
|
|
89
89
|
while True:
|
|
90
|
-
if my[
|
|
91
|
-
if my[
|
|
90
|
+
if my[leaf1ndex] <= 1 or track[leafBelow][0] == 1:
|
|
91
|
+
if my[leaf1ndex] > the[leavesTotal]:
|
|
92
92
|
foldingsTotal += the[leavesTotal]
|
|
93
93
|
else:
|
|
94
94
|
my[unconstrainedLeaf] = 0
|
|
95
|
-
my[gap1ndexCeiling] = track[gapRangeStart][my[
|
|
96
|
-
my[
|
|
95
|
+
my[gap1ndexCeiling] = track[gapRangeStart][my[leaf1ndex] - 1]
|
|
96
|
+
my[gap1ndex] = my[gap1ndexCeiling]
|
|
97
97
|
|
|
98
98
|
for PREPAREdimension1ndex in range(1, the[dimensionsPlus1]):
|
|
99
|
-
if connectionGraph[PREPAREdimension1ndex][my[
|
|
99
|
+
if connectionGraph[PREPAREdimension1ndex][my[leaf1ndex]][my[leaf1ndex]] == my[leaf1ndex]:
|
|
100
100
|
my[unconstrainedLeaf] += 1
|
|
101
101
|
else:
|
|
102
|
-
my[
|
|
103
|
-
while my[
|
|
102
|
+
my[leafConnectee] = connectionGraph[PREPAREdimension1ndex][my[leaf1ndex]][my[leaf1ndex]]
|
|
103
|
+
while my[leafConnectee] != my[leaf1ndex]:
|
|
104
104
|
|
|
105
|
-
if my[
|
|
105
|
+
if my[leafConnectee] != my[leaf1ndex]:
|
|
106
106
|
my[dimension1ndex] = PREPAREdimension1ndex
|
|
107
107
|
return track, gapsWhere, my
|
|
108
108
|
|
|
109
|
-
if my[
|
|
110
|
-
gapsWhere[my[gap1ndexCeiling]] = my[
|
|
111
|
-
if track[countDimensionsGapped][my[
|
|
109
|
+
if my[leaf1ndex] != the[leavesTotal]:
|
|
110
|
+
gapsWhere[my[gap1ndexCeiling]] = my[leafConnectee]
|
|
111
|
+
if track[countDimensionsGapped][my[leafConnectee]] == 0:
|
|
112
112
|
my[gap1ndexCeiling] += 1
|
|
113
|
-
track[countDimensionsGapped][my[
|
|
113
|
+
track[countDimensionsGapped][my[leafConnectee]] += 1
|
|
114
114
|
else:
|
|
115
115
|
print("else")
|
|
116
116
|
my[dimension1ndex] = PREPAREdimension1ndex
|
|
117
117
|
return track, gapsWhere, my
|
|
118
|
-
# PREPAREmy[
|
|
119
|
-
my[
|
|
118
|
+
# PREPAREmy[leafConnectee] % the[leavesTotal] == PREPAREmy[taskIndex]
|
|
119
|
+
my[leafConnectee] = connectionGraph[dimension1ndex][my[leaf1ndex]][track[leafBelow][my[leafConnectee]]]
|
|
120
120
|
|
|
121
121
|
if my[unconstrainedLeaf] == the[dimensionsTotal]:
|
|
122
|
-
for
|
|
123
|
-
gapsWhere[my[gap1ndexCeiling]] =
|
|
122
|
+
for indexLeaf in range(my[leaf1ndex]):
|
|
123
|
+
gapsWhere[my[gap1ndexCeiling]] = indexLeaf
|
|
124
124
|
my[gap1ndexCeiling] += 1
|
|
125
125
|
|
|
126
|
-
for indexMiniGap in range(my[
|
|
127
|
-
gapsWhere[my[
|
|
126
|
+
for indexMiniGap in range(my[gap1ndex], my[gap1ndexCeiling]):
|
|
127
|
+
gapsWhere[my[gap1ndex]] = gapsWhere[indexMiniGap]
|
|
128
128
|
if track[countDimensionsGapped][gapsWhere[indexMiniGap]] == the[dimensionsTotal] - my[unconstrainedLeaf]:
|
|
129
|
-
my[
|
|
129
|
+
my[gap1ndex] += 1
|
|
130
130
|
track[countDimensionsGapped][gapsWhere[indexMiniGap]] = 0
|
|
131
131
|
|
|
132
|
-
while my[
|
|
133
|
-
my[
|
|
134
|
-
track[leafBelow][track[leafAbove][my[
|
|
135
|
-
track[leafAbove][track[leafBelow][my[
|
|
132
|
+
while my[leaf1ndex] > 0 and my[gap1ndex] == track[gapRangeStart][my[leaf1ndex] - 1]:
|
|
133
|
+
my[leaf1ndex] -= 1
|
|
134
|
+
track[leafBelow][track[leafAbove][my[leaf1ndex]]] = track[leafBelow][my[leaf1ndex]]
|
|
135
|
+
track[leafAbove][track[leafBelow][my[leaf1ndex]]] = track[leafAbove][my[leaf1ndex]]
|
|
136
136
|
|
|
137
|
-
if my[
|
|
138
|
-
my[
|
|
139
|
-
track[leafAbove][my[
|
|
140
|
-
track[leafBelow][my[
|
|
141
|
-
track[leafBelow][track[leafAbove][my[
|
|
142
|
-
track[leafAbove][track[leafBelow][my[
|
|
143
|
-
track[gapRangeStart][my[
|
|
144
|
-
my[
|
|
137
|
+
if my[leaf1ndex] > 0:
|
|
138
|
+
my[gap1ndex] -= 1
|
|
139
|
+
track[leafAbove][my[leaf1ndex]] = gapsWhere[my[gap1ndex]]
|
|
140
|
+
track[leafBelow][my[leaf1ndex]] = track[leafBelow][track[leafAbove][my[leaf1ndex]]]
|
|
141
|
+
track[leafBelow][track[leafAbove][my[leaf1ndex]]] = my[leaf1ndex]
|
|
142
|
+
track[leafAbove][track[leafBelow][my[leaf1ndex]]] = my[leaf1ndex]
|
|
143
|
+
track[gapRangeStart][my[leaf1ndex]] = my[gap1ndex]
|
|
144
|
+
my[leaf1ndex] += 1
|
|
145
145
|
|
|
146
146
|
RETURNtrack, RETURNgapsWhere, RETURNmy = prepareWork(TEMPLATEtrack.copy(), TEMPLATEgapsWhere.copy(), TEMPLATEmy.copy())
|
|
147
147
|
|
|
@@ -166,44 +166,44 @@ def doWork(track: NDArray,
|
|
|
166
166
|
thisIsNotTheFirstPass = False
|
|
167
167
|
|
|
168
168
|
while papasGotABrandNewBag:
|
|
169
|
-
if my[
|
|
169
|
+
if my[leaf1ndex] <= 1 or track[leafBelow][0] == 1 or if_activeLeaf1ndex_LTE_1_or_leafBelow_index_0_equals_1 == True:
|
|
170
170
|
if_activeLeaf1ndex_LTE_1_or_leafBelow_index_0_equals_1 = False
|
|
171
|
-
if my[
|
|
171
|
+
if my[leaf1ndex] > the[leavesTotal] and thisIsNotTheFirstPass:
|
|
172
172
|
my[foldingsSubtotal] += the[leavesTotal]
|
|
173
173
|
else:
|
|
174
174
|
if thisIsNotTheFirstPass:
|
|
175
175
|
my[unconstrainedLeaf] = 0
|
|
176
|
-
my[gap1ndexCeiling] = track[gapRangeStart][my[
|
|
177
|
-
my[
|
|
176
|
+
my[gap1ndexCeiling] = track[gapRangeStart][my[leaf1ndex] - 1]
|
|
177
|
+
my[gap1ndex] = my[gap1ndexCeiling]
|
|
178
178
|
|
|
179
179
|
for_dimension1ndex_in_range_1_to_dimensionsPlus1 = True
|
|
180
180
|
while for_dimension1ndex_in_range_1_to_dimensionsPlus1 == True:
|
|
181
181
|
for_dimension1ndex_in_range_1_to_dimensionsPlus1 = False
|
|
182
|
-
if connectionGraph[my[dimension1ndex]][my[
|
|
182
|
+
if connectionGraph[my[dimension1ndex]][my[leaf1ndex]][my[leaf1ndex]] == my[leaf1ndex] and thisIsNotTheFirstPass:
|
|
183
183
|
my[unconstrainedLeaf] += 1
|
|
184
184
|
else:
|
|
185
185
|
if thisIsNotTheFirstPass:
|
|
186
|
-
my[
|
|
187
|
-
if my[
|
|
186
|
+
my[leafConnectee] = connectionGraph[my[dimension1ndex]][my[leaf1ndex]][my[leaf1ndex]]
|
|
187
|
+
if my[leafConnectee] != my[leaf1ndex]:
|
|
188
188
|
while_leaf1ndexConnectee_notEquals_activeLeaf1ndex = True
|
|
189
189
|
|
|
190
190
|
while while_leaf1ndexConnectee_notEquals_activeLeaf1ndex == True:
|
|
191
191
|
while_leaf1ndexConnectee_notEquals_activeLeaf1ndex = False
|
|
192
192
|
thisIsNotTheFirstPass = True
|
|
193
|
-
if taskDivisions==0 or my[
|
|
193
|
+
if taskDivisions==0 or my[leaf1ndex] != taskDivisions:
|
|
194
194
|
myTask = True
|
|
195
195
|
else:
|
|
196
|
-
modulo = my[
|
|
196
|
+
modulo = my[leafConnectee] % the[leavesTotal]
|
|
197
197
|
if modulo == my[taskIndex]: myTask = True
|
|
198
198
|
else:
|
|
199
199
|
myTask = False
|
|
200
200
|
if myTask:
|
|
201
|
-
gapsWhere[my[gap1ndexCeiling]] = my[
|
|
202
|
-
if track[countDimensionsGapped][my[
|
|
201
|
+
gapsWhere[my[gap1ndexCeiling]] = my[leafConnectee]
|
|
202
|
+
if track[countDimensionsGapped][my[leafConnectee]] == 0:
|
|
203
203
|
my[gap1ndexCeiling] += 1
|
|
204
|
-
track[countDimensionsGapped][my[
|
|
205
|
-
my[
|
|
206
|
-
if my[
|
|
204
|
+
track[countDimensionsGapped][my[leafConnectee]] += 1
|
|
205
|
+
my[leafConnectee] = connectionGraph[my[dimension1ndex]][my[leaf1ndex]][track[leafBelow][my[leafConnectee]]]
|
|
206
|
+
if my[leafConnectee] != my[leaf1ndex]:
|
|
207
207
|
while_leaf1ndexConnectee_notEquals_activeLeaf1ndex = True
|
|
208
208
|
my[dimension1ndex] += 1
|
|
209
209
|
if my[dimension1ndex] < the[dimensionsPlus1]:
|
|
@@ -212,29 +212,29 @@ def doWork(track: NDArray,
|
|
|
212
212
|
my[dimension1ndex] = 1
|
|
213
213
|
|
|
214
214
|
if my[unconstrainedLeaf] == the[dimensionsTotal]:
|
|
215
|
-
for leaf1ndex in range(my[
|
|
215
|
+
for leaf1ndex in range(my[leaf1ndex]):
|
|
216
216
|
gapsWhere[my[gap1ndexCeiling]] = leaf1ndex
|
|
217
217
|
my[gap1ndexCeiling] += 1
|
|
218
218
|
|
|
219
|
-
for indexMiniGap in range(my[
|
|
220
|
-
gapsWhere[my[
|
|
219
|
+
for indexMiniGap in range(my[gap1ndex], my[gap1ndexCeiling]):
|
|
220
|
+
gapsWhere[my[gap1ndex]] = gapsWhere[indexMiniGap]
|
|
221
221
|
if track[countDimensionsGapped][gapsWhere[indexMiniGap]] == the[dimensionsTotal] - my[unconstrainedLeaf]:
|
|
222
|
-
my[
|
|
222
|
+
my[gap1ndex] += 1
|
|
223
223
|
track[countDimensionsGapped][gapsWhere[indexMiniGap]] = 0
|
|
224
224
|
|
|
225
|
-
while my[
|
|
226
|
-
my[
|
|
227
|
-
track[leafBelow][track[leafAbove][my[
|
|
228
|
-
track[leafAbove][track[leafBelow][my[
|
|
229
|
-
|
|
230
|
-
if my[
|
|
231
|
-
my[
|
|
232
|
-
track[leafAbove][my[
|
|
233
|
-
track[leafBelow][my[
|
|
234
|
-
track[leafBelow][track[leafAbove][my[
|
|
235
|
-
track[leafAbove][track[leafBelow][my[
|
|
236
|
-
track[gapRangeStart][my[
|
|
237
|
-
my[
|
|
238
|
-
|
|
239
|
-
if my[
|
|
225
|
+
while my[leaf1ndex] > 0 and my[gap1ndex] == track[gapRangeStart][my[leaf1ndex] - 1]:
|
|
226
|
+
my[leaf1ndex] -= 1
|
|
227
|
+
track[leafBelow][track[leafAbove][my[leaf1ndex]]] = track[leafBelow][my[leaf1ndex]]
|
|
228
|
+
track[leafAbove][track[leafBelow][my[leaf1ndex]]] = track[leafAbove][my[leaf1ndex]]
|
|
229
|
+
|
|
230
|
+
if my[leaf1ndex] > 0:
|
|
231
|
+
my[gap1ndex] -= 1
|
|
232
|
+
track[leafAbove][my[leaf1ndex]] = gapsWhere[my[gap1ndex]]
|
|
233
|
+
track[leafBelow][my[leaf1ndex]] = track[leafBelow][track[leafAbove][my[leaf1ndex]]]
|
|
234
|
+
track[leafBelow][track[leafAbove][my[leaf1ndex]]] = my[leaf1ndex]
|
|
235
|
+
track[leafAbove][track[leafBelow][my[leaf1ndex]]] = my[leaf1ndex]
|
|
236
|
+
track[gapRangeStart][my[leaf1ndex]] = my[gap1ndex]
|
|
237
|
+
my[leaf1ndex] += 1
|
|
238
|
+
|
|
239
|
+
if my[leaf1ndex] <= 0:
|
|
240
240
|
return my[foldingsSubtotal]
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from numba import njit
|
|
2
|
+
import numpy
|
|
3
|
+
|
|
4
|
+
@njit(cache=True)
|
|
5
|
+
def foldings_plus_1(p: list[int], computationDivisions: int = 0, computationIndex: int = 0) -> int:
|
|
6
|
+
n: int = 1 # Total number of leaves
|
|
7
|
+
for dimension in p:
|
|
8
|
+
n *= dimension
|
|
9
|
+
|
|
10
|
+
d = len(p) # Number of dimensions
|
|
11
|
+
# Compute arrays P, C, D as per the algorithm
|
|
12
|
+
P = numpy.ones(d + 1, dtype=numpy.int64)
|
|
13
|
+
for i in range(1, d + 1):
|
|
14
|
+
P[i] = P[i - 1] * p[i - 1]
|
|
15
|
+
|
|
16
|
+
# C[i][m] holds the i-th coordinate of leaf m
|
|
17
|
+
C = numpy.zeros((d + 1, n + 1), dtype=numpy.int64)
|
|
18
|
+
for i in range(1, d + 1):
|
|
19
|
+
for m in range(1, n + 1):
|
|
20
|
+
C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i - 1] + 1
|
|
21
|
+
|
|
22
|
+
# D[i][l][m] computes the leaf connected to m in section i when inserting l
|
|
23
|
+
D = numpy.zeros((d + 1, n + 1, n + 1), dtype=numpy.int64)
|
|
24
|
+
for i in range(1, d + 1):
|
|
25
|
+
for l in range(1, n + 1):
|
|
26
|
+
for m in range(1, l + 1):
|
|
27
|
+
delta = C[i][l] - C[i][m]
|
|
28
|
+
if delta % 2 == 0:
|
|
29
|
+
# If delta is even
|
|
30
|
+
if C[i][m] == 1:
|
|
31
|
+
D[i][l][m] = m
|
|
32
|
+
else:
|
|
33
|
+
D[i][l][m] = m - P[i - 1]
|
|
34
|
+
else:
|
|
35
|
+
# If delta is odd
|
|
36
|
+
if C[i][m] == p[i - 1] or m + P[i - 1] > l:
|
|
37
|
+
D[i][l][m] = m
|
|
38
|
+
else:
|
|
39
|
+
D[i][l][m] = m + P[i - 1]
|
|
40
|
+
# Initialize arrays/lists
|
|
41
|
+
A = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf above leaf m
|
|
42
|
+
B = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf below leaf m
|
|
43
|
+
count = numpy.zeros(n + 1, dtype=numpy.int64) # Counts for potential gaps
|
|
44
|
+
gapter = numpy.zeros(n + 1, dtype=numpy.int64) # Indices for gap stack per leaf
|
|
45
|
+
gap = numpy.zeros(n * n + 1, dtype=numpy.int64) # Stack of potential gaps
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Initialize variables for backtracking
|
|
49
|
+
total_count = 0 # Total number of foldings
|
|
50
|
+
g = 0 # Gap index
|
|
51
|
+
l = 1 # Current leaf
|
|
52
|
+
|
|
53
|
+
# Start backtracking loop
|
|
54
|
+
while l > 0:
|
|
55
|
+
# If we have processed all leaves, increment total count
|
|
56
|
+
if l > n:
|
|
57
|
+
total_count += 1
|
|
58
|
+
else:
|
|
59
|
+
dd = 0 # Number of sections where leaf l is unconstrained
|
|
60
|
+
gg = g # Temporary gap index
|
|
61
|
+
g = gapter[l - 1] # Reset gap index for current leaf
|
|
62
|
+
|
|
63
|
+
# Count possible gaps for leaf l in each section
|
|
64
|
+
for i in range(1, d + 1):
|
|
65
|
+
if D[i][l][l] == l:
|
|
66
|
+
dd += 1
|
|
67
|
+
else:
|
|
68
|
+
m = D[i][l][l]
|
|
69
|
+
while m != l:
|
|
70
|
+
if computationDivisions == 0 or l != computationDivisions or m % computationDivisions == computationIndex:
|
|
71
|
+
gap[gg] = m
|
|
72
|
+
if count[m] == 0:
|
|
73
|
+
gg += 1
|
|
74
|
+
count[m] += 1
|
|
75
|
+
m = D[i][l][B[m]]
|
|
76
|
+
|
|
77
|
+
# If leaf l is unconstrained in all sections, it can be inserted anywhere
|
|
78
|
+
if dd == d:
|
|
79
|
+
for m in range(l):
|
|
80
|
+
gap[gg] = m
|
|
81
|
+
gg += 1
|
|
82
|
+
|
|
83
|
+
# Filter gaps that are common to all sections
|
|
84
|
+
for j in range(g, gg):
|
|
85
|
+
gap[g] = gap[j]
|
|
86
|
+
if count[gap[j]] == d - dd:
|
|
87
|
+
g += 1
|
|
88
|
+
count[gap[j]] = 0 # Reset count for next iteration
|
|
89
|
+
|
|
90
|
+
# Recursive backtracking steps
|
|
91
|
+
while l > 0 and g == gapter[l - 1]:
|
|
92
|
+
l -= 1
|
|
93
|
+
B[A[l]] = B[l]
|
|
94
|
+
A[B[l]] = A[l]
|
|
95
|
+
|
|
96
|
+
if l > 0:
|
|
97
|
+
g -= 1
|
|
98
|
+
A[l] = gap[g]
|
|
99
|
+
B[l] = B[A[l]]
|
|
100
|
+
B[A[l]] = l
|
|
101
|
+
A[B[l]] = l
|
|
102
|
+
gapter[l] = g # Save current gap index
|
|
103
|
+
l += 1 # Move to next leaf
|
|
104
|
+
|
|
105
|
+
return total_count
|
|
106
|
+
|
|
107
|
+
@njit(cache=True)
|
|
108
|
+
def foldings(p: list[int], computationDivisions: int = 0, computationIndex: int = 0) -> int:
|
|
109
|
+
n: int = 1 # Total number of leaves
|
|
110
|
+
for dimension in p:
|
|
111
|
+
n *= dimension
|
|
112
|
+
|
|
113
|
+
d = len(p) # Number of dimensions
|
|
114
|
+
# Compute arrays P, C, D as per the algorithm
|
|
115
|
+
P = numpy.ones(d + 1, dtype=numpy.int64)
|
|
116
|
+
for i in range(1, d + 1):
|
|
117
|
+
P[i] = P[i - 1] * p[i - 1]
|
|
118
|
+
|
|
119
|
+
# C[i][m] holds the i-th coordinate of leaf m
|
|
120
|
+
C = numpy.zeros((d + 1, n + 1), dtype=numpy.int64)
|
|
121
|
+
for i in range(1, d + 1):
|
|
122
|
+
for m in range(1, n + 1):
|
|
123
|
+
C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i - 1] + 1
|
|
124
|
+
# C[i][m] = ((m - 1) // P[i - 1]) % p[i - 1] + 1 # NOTE different, but either one works
|
|
125
|
+
|
|
126
|
+
# D[i][l][m] computes the leaf connected to m in section i when inserting l
|
|
127
|
+
D = numpy.zeros((d + 1, n + 1, n + 1), dtype=numpy.int64)
|
|
128
|
+
for i in range(1, d + 1):
|
|
129
|
+
for l in range(1, n + 1):
|
|
130
|
+
for m in range(1, l + 1):
|
|
131
|
+
delta = C[i][l] - C[i][m]
|
|
132
|
+
if delta % 2 == 0:
|
|
133
|
+
# If delta is even
|
|
134
|
+
if C[i][m] == 1:
|
|
135
|
+
D[i][l][m] = m
|
|
136
|
+
else:
|
|
137
|
+
D[i][l][m] = m - P[i - 1]
|
|
138
|
+
else:
|
|
139
|
+
# If delta is odd
|
|
140
|
+
if C[i][m] == p[i - 1] or m + P[i - 1] > l:
|
|
141
|
+
D[i][l][m] = m
|
|
142
|
+
else:
|
|
143
|
+
D[i][l][m] = m + P[i - 1]
|
|
144
|
+
# Initialize arrays/lists
|
|
145
|
+
A = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf above leaf m
|
|
146
|
+
B = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf below leaf m
|
|
147
|
+
count = numpy.zeros(n + 1, dtype=numpy.int64) # Counts for potential gaps
|
|
148
|
+
gapter = numpy.zeros(n + 1, dtype=numpy.int64) # Indices for gap stack per leaf
|
|
149
|
+
gap = numpy.zeros(n * n + 1, dtype=numpy.int64) # Stack of potential gaps
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Initialize variables for backtracking
|
|
153
|
+
total_count = 0 # Total number of foldings
|
|
154
|
+
g = 0 # Gap index
|
|
155
|
+
l = 1 # Current leaf
|
|
156
|
+
|
|
157
|
+
# Start backtracking loop
|
|
158
|
+
while l > 0:
|
|
159
|
+
if l <= 1 or B[0] == 1: # NOTE different
|
|
160
|
+
# NOTE the above `if` statement encloses the the if/else block below
|
|
161
|
+
# NOTE these changes increase the throughput by more than an order of magnitude
|
|
162
|
+
if l > n:
|
|
163
|
+
total_count += n
|
|
164
|
+
else:
|
|
165
|
+
dd = 0 # Number of sections where leaf l is unconstrained
|
|
166
|
+
gg = gapter[l - 1] # Track possible gaps # NOTE different, but not important
|
|
167
|
+
g = gg # NOTE different, but not important
|
|
168
|
+
|
|
169
|
+
# Count possible gaps for leaf l in each section
|
|
170
|
+
for i in range(1, d + 1):
|
|
171
|
+
if D[i][l][l] == l:
|
|
172
|
+
dd += 1
|
|
173
|
+
else:
|
|
174
|
+
m = D[i][l][l]
|
|
175
|
+
while m != l:
|
|
176
|
+
if computationDivisions == 0 or l != computationDivisions or m % computationDivisions == computationIndex:
|
|
177
|
+
gap[gg] = m
|
|
178
|
+
if count[m] == 0:
|
|
179
|
+
gg += 1
|
|
180
|
+
count[m] += 1
|
|
181
|
+
m = D[i][l][B[m]]
|
|
182
|
+
|
|
183
|
+
# If leaf l is unconstrained in all sections, it can be inserted anywhere
|
|
184
|
+
if dd == d:
|
|
185
|
+
for m in range(l):
|
|
186
|
+
gap[gg] = m
|
|
187
|
+
gg += 1
|
|
188
|
+
|
|
189
|
+
# Filter gaps that are common to all sections
|
|
190
|
+
for j in range(g, gg):
|
|
191
|
+
gap[g] = gap[j]
|
|
192
|
+
if count[gap[j]] == d - dd:
|
|
193
|
+
g += 1
|
|
194
|
+
count[gap[j]] = 0 # Reset count for next iteration
|
|
195
|
+
|
|
196
|
+
# Recursive backtracking steps
|
|
197
|
+
while l > 0 and g == gapter[l - 1]:
|
|
198
|
+
l -= 1
|
|
199
|
+
B[A[l]] = B[l]
|
|
200
|
+
A[B[l]] = A[l]
|
|
201
|
+
|
|
202
|
+
if l > 0:
|
|
203
|
+
g -= 1
|
|
204
|
+
A[l] = gap[g]
|
|
205
|
+
B[l] = B[A[l]]
|
|
206
|
+
B[A[l]] = l
|
|
207
|
+
A[B[l]] = l
|
|
208
|
+
gapter[l] = g # Save current gap index
|
|
209
|
+
l += 1 # Move to next leaf
|
|
210
|
+
|
|
211
|
+
return total_count
|
mapFolding/startHere.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from mapFolding import
|
|
1
|
+
from mapFolding import outfitCountFolds, getFilenameFoldsTotal
|
|
2
2
|
from typing import Optional, Sequence, Type, Union
|
|
3
3
|
import os
|
|
4
4
|
import pathlib
|
|
@@ -13,9 +13,9 @@ def countFolds(listDimensions: Sequence[int], writeFoldsTotal: Optional[Union[st
|
|
|
13
13
|
computationDivisions (None):
|
|
14
14
|
Whether and how to divide the computational work. See notes for details.
|
|
15
15
|
CPUlimit (None): This is only relevant if there are `computationDivisions`: whether and how to limit the CPU usage. See notes for details.
|
|
16
|
-
**keywordArguments: Additional arguments including dtypeDefault and dtypeLarge for data type specifications.
|
|
16
|
+
**keywordArguments: Additional arguments including `dtypeDefault` and `dtypeLarge` for data type specifications.
|
|
17
17
|
Returns:
|
|
18
|
-
|
|
18
|
+
foldsSubTotals: Total number of distinct ways to fold a map of the given dimensions.
|
|
19
19
|
|
|
20
20
|
Computation divisions:
|
|
21
21
|
- None: no division of the computation into tasks; sets task divisions to 0
|
|
@@ -34,29 +34,29 @@ def countFolds(listDimensions: Sequence[int], writeFoldsTotal: Optional[Union[st
|
|
|
34
34
|
N.B.: You probably don't want to divide the computation into tasks.
|
|
35
35
|
If you want to compute a large `foldsTotal`, dividing the computation into tasks is usually a bad idea. Dividing the algorithm into tasks is inherently inefficient: efficient division into tasks means there would be no overlap in the work performed by each task. When dividing this algorithm, the amount of overlap is between 50% and 90% by all tasks: at least 50% of the work done by every task must be done by _all_ tasks. If you improve the computation time, it will only change by -10 to -50% depending on (at the very least) the ratio of the map dimensions and the number of leaves. If an undivided computation would take 10 hours on your computer, for example, the computation will still take at least 5 hours but you might reduce the time to 9 hours. Most of the time, however, you will increase the computation time. If logicalCores >= leavesTotal, it will probably be faster. If logicalCores <= 2 * leavesTotal, it will almost certainly be slower for all map dimensions.
|
|
36
36
|
"""
|
|
37
|
-
stateUniversal =
|
|
37
|
+
stateUniversal = outfitCountFolds(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit, **keywordArguments)
|
|
38
38
|
|
|
39
39
|
pathFilenameFoldsTotal = None
|
|
40
40
|
if writeFoldsTotal is not None:
|
|
41
41
|
pathFilenameFoldsTotal = pathlib.Path(writeFoldsTotal)
|
|
42
42
|
if pathFilenameFoldsTotal.is_dir():
|
|
43
|
-
filenameFoldsTotalDEFAULT =
|
|
43
|
+
filenameFoldsTotalDEFAULT = getFilenameFoldsTotal(stateUniversal['mapShape'])
|
|
44
44
|
pathFilenameFoldsTotal = pathFilenameFoldsTotal / filenameFoldsTotalDEFAULT
|
|
45
45
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
46
46
|
|
|
47
47
|
# NOTE Don't import a module with a numba.jit function until you want the function to compile and to freeze all settings for that function.
|
|
48
48
|
from mapFolding.babbage import _countFolds
|
|
49
|
-
|
|
50
|
-
#
|
|
49
|
+
_countFolds(**stateUniversal)
|
|
50
|
+
# foldsSubTotals = benchmarkSherpa(**stateUniversal)
|
|
51
|
+
|
|
52
|
+
foldsTotal = stateUniversal['foldsSubTotals'].sum().item()
|
|
51
53
|
|
|
52
54
|
if pathFilenameFoldsTotal is not None:
|
|
53
55
|
try:
|
|
54
56
|
pathFilenameFoldsTotal.write_text(str(foldsTotal))
|
|
55
57
|
except Exception as ERRORmessage:
|
|
56
58
|
print(ERRORmessage)
|
|
57
|
-
print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
58
|
-
print(f"{foldsTotal=}")
|
|
59
|
-
print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
59
|
+
print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
60
60
|
|
|
61
61
|
return foldsTotal
|
|
62
62
|
|
|
@@ -65,6 +65,6 @@ def countFolds(listDimensions: Sequence[int], writeFoldsTotal: Optional[Union[st
|
|
|
65
65
|
# from typing import Any, Tuple
|
|
66
66
|
# from mapFolding.benchmarks.benchmarking import recordBenchmarks
|
|
67
67
|
# @recordBenchmarks()
|
|
68
|
-
# def benchmarkSherpa(connectionGraph: NDArray[integer[Any]],
|
|
68
|
+
# def benchmarkSherpa(connectionGraph: NDArray[integer[Any]], foldsSubTotals: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], mapShape: Tuple[int, ...], my: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]):
|
|
69
69
|
# from mapFolding.babbage import _countFolds
|
|
70
|
-
# return _countFolds(connectionGraph,
|
|
70
|
+
# return _countFolds(connectionGraph, foldsSubTotals, gapsWhere, mapShape, my, the, track)
|
mapFolding/theSSOT.py
CHANGED
|
@@ -5,13 +5,17 @@ import numpy.typing
|
|
|
5
5
|
import pathlib
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
|
+
dtypeLarge = numpy.int64
|
|
9
|
+
dtypeDefault = dtypeLarge
|
|
10
|
+
dtypeSmall = dtypeDefault
|
|
11
|
+
|
|
8
12
|
try:
|
|
9
13
|
_pathModule = pathlib.Path(__file__).parent
|
|
10
14
|
except NameError:
|
|
11
15
|
_pathModule = pathlib.Path.cwd()
|
|
12
16
|
|
|
13
17
|
pathJobDEFAULT = _pathModule / "jobs"
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
if 'google.colab' in sys.modules:
|
|
16
20
|
pathJobDEFAULT = pathlib.Path("/content/drive/MyDrive") / "jobs"
|
|
17
21
|
|
|
@@ -24,7 +28,8 @@ class EnumIndices(enum.IntEnum):
|
|
|
24
28
|
return count
|
|
25
29
|
|
|
26
30
|
def __index__(self) -> int:
|
|
27
|
-
"""
|
|
31
|
+
"""Adapt enum to the ultra-rare event of indexing a NumPy 'ndarray', which is not the
|
|
32
|
+
same as `array.array`. See NumPy.org; I think it will be very popular someday."""
|
|
28
33
|
return self.value
|
|
29
34
|
|
|
30
35
|
class indexMy(EnumIndices):
|
|
@@ -54,7 +59,7 @@ class indexTrack(EnumIndices):
|
|
|
54
59
|
|
|
55
60
|
class computationState(TypedDict):
|
|
56
61
|
connectionGraph: numpy.typing.NDArray[numpy.integer[Any]]
|
|
57
|
-
|
|
62
|
+
foldsSubTotals: numpy.ndarray[numpy.int64, numpy.dtype[numpy.int64]]
|
|
58
63
|
mapShape: Tuple[int, ...]
|
|
59
64
|
my: numpy.typing.NDArray[numpy.integer[Any]]
|
|
60
65
|
gapsWhere: numpy.typing.NDArray[numpy.integer[Any]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mapFolding
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Algorithm(s) for counting distinct ways to fold a map (or a strip of stamps)
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
Project-URL: homepage, https://github.com/hunterhogan/mapFolding
|
|
@@ -25,7 +25,7 @@ Requires-Dist: pytest-xdist; extra == "testing"
|
|
|
25
25
|
|
|
26
26
|
# Algorithm(s) for counting distinct ways to fold a map (or a strip of stamps)
|
|
27
27
|
|
|
28
|
-
`mapFolding.countFolds()`
|
|
28
|
+
The function `mapFolding.countFolds()` counts distinct ways to fold maps and strips of stamps. The function accepts two or more dimensions:
|
|
29
29
|
|
|
30
30
|
```python
|
|
31
31
|
from mapFolding import countFolds
|
|
@@ -34,12 +34,12 @@ foldsTotal = countFolds( [2,10] )
|
|
|
34
34
|
|
|
35
35
|
The directory [mapFolding/reference](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference) has
|
|
36
36
|
|
|
37
|
-
- a verbatim transcription of
|
|
37
|
+
- a verbatim transcription of Lunnon's "procedure" published in 1971 by _The Computer Journal_,
|
|
38
38
|
- multiple referential versions of the procedure with explanatory comments including
|
|
39
39
|
- [hunterNumba.py](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference), a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
|
|
40
40
|
- miscellaneous notes.
|
|
41
41
|
|
|
42
|
-
[](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml)
|
|
42
|
+
[](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml) [](https://pypi.org/project/mapFolding/)    
|
|
43
43
|
|
|
44
44
|
## Simple, easy usage based on OEIS IDs
|
|
45
45
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=wnf2EzHR2unVha6-Y0gRoSPaE4PDdT4VngINa_dfT2E,337
|
|
2
|
+
mapFolding/babbage.py,sha256=LCtyapF8SKsECqBifqbLm7bR_i4n8VJ647w_TQxnQvE,1930
|
|
3
|
+
mapFolding/beDRY.py,sha256=UE4IRrb5lXN4nTuUghHfieNm2FBHgz7oBj_EqUgkadI,13800
|
|
4
|
+
mapFolding/lovelace.py,sha256=r4dkTGh_AgaVsWOsdPRdpPWVx3TjVjbWfPCVu3UTm6U,13495
|
|
5
|
+
mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
|
|
6
|
+
mapFolding/startHere.py,sha256=glGxmefrWpxyuqrzXrbCvIo84yvPnv8l8l7Rmff2sAo,5105
|
|
7
|
+
mapFolding/theSSOT.py,sha256=rAyx034y33QC7IiRKaW89CMGGvqe2p-QBc3_fO-zEGM,2101
|
|
8
|
+
mapFolding/JAX/lunnanJAX.py,sha256=xMZloN47q-MVfjdYOM1hi9qR4OnLq7qALmGLMraevQs,14819
|
|
9
|
+
mapFolding/JAX/taskJAX.py,sha256=yJNeH0rL6EhJ6ppnATHF0Zf81CDMC10bnPnimVxE1hc,20037
|
|
10
|
+
mapFolding/benchmarks/benchmarking.py,sha256=kv85F6V9pGhZvTOImArOuxyg5rywA_T6JLH_qFXM8BM,3018
|
|
11
|
+
mapFolding/benchmarks/test_benchmarks.py,sha256=c4ANeR3jgqpKXFoxDeZkmAHxSuenMwsjmrhKJ1_XPqY,3659
|
|
12
|
+
mapFolding/reference/flattened.py,sha256=X9nvRzg7YDcpCtSDTL4YiidjshlX9rg2e6JVCY6i2u0,16547
|
|
13
|
+
mapFolding/reference/hunterNumba.py,sha256=0giUyqAFzP-XKcq3Kz8wIWCK0BVFhjABVJ1s-w4Jhu0,7109
|
|
14
|
+
mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
|
|
15
|
+
mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
|
|
16
|
+
mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
|
|
17
|
+
mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
|
|
18
|
+
mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
|
|
19
|
+
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
|
|
20
|
+
tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
|
|
21
|
+
tests/conftest.py,sha256=WE3DETPaQ4zuUiw8pweTry4rp1PxvUPgeb8CN8eh5JI,13574
|
|
22
|
+
tests/pythons_idiotic_namespace.py,sha256=oOLDBergQqqhGuRpsXUnFD-R_6AlJipNKYHw-kk_OKw,33
|
|
23
|
+
tests/test_oeis.py,sha256=vxnwO-cSR68htkyMh9QMVv-lvxBo6qlwPg1Rbx4JylY,7963
|
|
24
|
+
tests/test_other.py,sha256=5DwOZsjezBHZzr4-9qEnybBYJEGsozBm2n9p0KYvs9E,10664
|
|
25
|
+
tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
|
|
26
|
+
mapFolding-0.2.3.dist-info/METADATA,sha256=nEQXgDmmuu2NJko4R4gPUdjIrm1upUGwMIOn0s0JOOc,6442
|
|
27
|
+
mapFolding-0.2.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
28
|
+
mapFolding-0.2.3.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
29
|
+
mapFolding-0.2.3.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
30
|
+
mapFolding-0.2.3.dist-info/RECORD,,
|
tests/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
from .conftest import makeDictionaryFoldsTotalKnown
|
|
1
|
+
from tests.conftest import makeDictionaryFoldsTotalKnown
|