mapFolding 0.2.0__py3-none-any.whl → 0.2.2__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 +1 -9
- mapFolding/babbage.py +19 -1
- mapFolding/beDRY.py +121 -70
- mapFolding/lovelace.py +41 -17
- mapFolding/oeis.py +59 -35
- 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 +37 -23
- mapFolding/theSSOT.py +6 -1
- {mapFolding-0.2.0.dist-info → mapFolding-0.2.2.dist-info}/METADATA +9 -46
- mapFolding-0.2.2.dist-info/RECORD +28 -0
- tests/conftest.py +95 -37
- tests/test_oeis.py +25 -26
- tests/test_other.py +43 -9
- tests/test_tasks.py +19 -6
- mapFolding/importPackages.py +0 -5
- mapFolding-0.2.0.dist-info/RECORD +0 -28
- {mapFolding-0.2.0.dist-info → mapFolding-0.2.2.dist-info}/WHEEL +0 -0
- {mapFolding-0.2.0.dist-info → mapFolding-0.2.2.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.2.0.dist-info → mapFolding-0.2.2.dist-info}/top_level.txt +0 -0
|
@@ -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,27 +1,38 @@
|
|
|
1
|
-
from numpy import integer
|
|
2
|
-
from numpy.typing import NDArray
|
|
3
|
-
from typing import Any, Tuple
|
|
4
|
-
import numba
|
|
5
|
-
import numpy
|
|
6
1
|
from mapFolding import outfitFoldings
|
|
7
|
-
|
|
8
|
-
from typing import Optional, Union, Sequence, Type
|
|
2
|
+
from typing import Optional, Sequence, Type, Union
|
|
9
3
|
import os
|
|
10
4
|
import pathlib
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
def countFolds(listDimensions: Sequence[int], writeFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None, computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[int, float, bool]] = None, **keywordArguments: Optional[Type]) -> int:
|
|
7
|
+
"""Count the total number of possible foldings for a given map dimensions.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
listDimensions: List of integers representing the dimensions of the map to be folded.
|
|
11
|
+
writeFoldsTotal (None): Path or filename to write the total fold count.
|
|
12
|
+
If a directory is provided, creates a file with default name based on map dimensions.
|
|
13
|
+
computationDivisions (None):
|
|
14
|
+
Whether and how to divide the computational work. See notes for details.
|
|
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.
|
|
17
|
+
Returns:
|
|
18
|
+
foldsTotal: Total number of distinct ways to fold a map of the given dimensions.
|
|
19
|
+
|
|
20
|
+
Computation divisions:
|
|
21
|
+
- None: no division of the computation into tasks; sets task divisions to 0
|
|
22
|
+
- int: direct set the number of task divisions; cannot exceed the map's total leaves
|
|
23
|
+
- "maximum": divides into `leavesTotal`-many `taskDivisions`
|
|
24
|
+
- "cpu": limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`
|
|
25
|
+
|
|
26
|
+
Limits on CPU usage `CPUlimit`:
|
|
27
|
+
- `False`, `None`, or `0`: No limits on CPU usage; uses all available CPUs. All other values will potentially limit CPU usage.
|
|
28
|
+
- `True`: Yes, limit the CPU usage; limits to 1 CPU.
|
|
29
|
+
- Integer `>= 1`: Limits usage to the specified number of CPUs.
|
|
30
|
+
- Decimal value (`float`) between 0 and 1: Fraction of total CPUs to use.
|
|
31
|
+
- Decimal value (`float`) between -1 and 0: Fraction of CPUs to *not* use.
|
|
32
|
+
- Integer `<= -1`: Subtract the absolute value from total CPUs.
|
|
33
|
+
|
|
34
|
+
N.B.: You probably don't want to divide the computation into tasks.
|
|
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.
|
|
25
36
|
"""
|
|
26
37
|
stateUniversal = outfitFoldings(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit, **keywordArguments)
|
|
27
38
|
|
|
@@ -33,6 +44,7 @@ def countFolds(
|
|
|
33
44
|
pathFilenameFoldsTotal = pathFilenameFoldsTotal / filenameFoldsTotalDEFAULT
|
|
34
45
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
35
46
|
|
|
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.
|
|
36
48
|
from mapFolding.babbage import _countFolds
|
|
37
49
|
foldsTotal = _countFolds(**stateUniversal)
|
|
38
50
|
# foldsTotal = benchmarkSherpa(**stateUniversal)
|
|
@@ -42,12 +54,14 @@ def countFolds(
|
|
|
42
54
|
pathFilenameFoldsTotal.write_text(str(foldsTotal))
|
|
43
55
|
except Exception as ERRORmessage:
|
|
44
56
|
print(ERRORmessage)
|
|
45
|
-
print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
46
|
-
print(f"{foldsTotal=}")
|
|
47
|
-
print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
57
|
+
print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
|
|
48
58
|
|
|
49
59
|
return foldsTotal
|
|
50
60
|
|
|
61
|
+
# from numpy import integer
|
|
62
|
+
# from numpy.typing import NDArray
|
|
63
|
+
# from typing import Any, Tuple
|
|
64
|
+
# from mapFolding.benchmarks.benchmarking import recordBenchmarks
|
|
51
65
|
# @recordBenchmarks()
|
|
52
66
|
# def benchmarkSherpa(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], mapShape: Tuple[int, ...], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]):
|
|
53
67
|
# from mapFolding.babbage import _countFolds
|
mapFolding/theSSOT.py
CHANGED
|
@@ -5,6 +5,10 @@ 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:
|
|
@@ -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):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mapFolding
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
@@ -32,11 +32,11 @@ from mapFolding import countFolds
|
|
|
32
32
|
foldsTotal = countFolds( [2,10] )
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
The directory
|
|
35
|
+
The directory [mapFolding/reference](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference) has
|
|
36
36
|
|
|
37
37
|
- a verbatim transcription of the "procedure" published in _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
42
|
[](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml)
|
|
@@ -97,13 +97,13 @@ Cache cleared from C:\apps\mapFolding\mapFolding\.cache
|
|
|
97
97
|
|
|
98
98
|
### The typo-laden algorithm published in 1971
|
|
99
99
|
|
|
100
|
-
The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
|
|
100
|
+
The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
|
|
101
101
|
|
|
102
|
-
In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
|
|
102
|
+
In [`foldings.txt`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
|
|
103
103
|
|
|
104
104
|
### Java implementation(s) and improvements
|
|
105
105
|
|
|
106
|
-
[archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
|
|
106
|
+
[archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
|
|
107
107
|
|
|
108
108
|
```java
|
|
109
109
|
/**
|
|
@@ -122,49 +122,12 @@ In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text tran
|
|
|
122
122
|
|
|
123
123
|
~~This caused my neurosis:~~ I enjoyed the following video, which is what introduced me to map folding.
|
|
124
124
|
|
|
125
|
-
"How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
|
|
125
|
+
"How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
|
|
126
126
|
|
|
127
127
|
[](https://www.youtube.com/watch?v=sfH9uIY3ln4)
|
|
128
128
|
|
|
129
|
-
##
|
|
130
|
-
|
|
131
|
-
### From Github
|
|
132
|
-
|
|
133
|
-
```sh
|
|
134
|
-
pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### From a local directory
|
|
138
|
-
|
|
139
|
-
#### Windows
|
|
140
|
-
|
|
141
|
-
```powershell
|
|
142
|
-
git clone https://github.com/hunterhogan/mapFolding.git \path\to\mapFolding
|
|
143
|
-
pip install mapFolding@file:\path\to\mapFolding
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
#### POSIX
|
|
147
|
-
|
|
148
|
-
```bash
|
|
149
|
-
git clone https://github.com/hunterhogan/mapFolding.git /path/to/mapFolding
|
|
150
|
-
pip install mapFolding@file:/path/to/mapFolding
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## Install updates
|
|
154
|
-
|
|
155
|
-
```sh
|
|
156
|
-
pip install --upgrade mapFolding@git+https://github.com/hunterhogan/mapFolding.git
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Creating a virtual environment before installation
|
|
160
|
-
|
|
161
|
-
You can isolate `mapFolding` in a virtual environment. For example, use the following commands to create a directory for the virtual environment, activate the virtual environment, and install the package. In the future, you will likely need to activate the virtual environment before using `mapFolding` again. From the command line, in a directory you want to install in.
|
|
129
|
+
## Installation
|
|
162
130
|
|
|
163
131
|
```sh
|
|
164
|
-
|
|
165
|
-
cd mapFolding
|
|
166
|
-
cd Scripts
|
|
167
|
-
activate
|
|
168
|
-
cd ..
|
|
169
|
-
pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
|
|
132
|
+
pip install mapFolding
|
|
170
133
|
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=POxXTSE6Qu1G8k-YlSXHzbe-_JGuHk-L9p5SLRng4vA,439
|
|
2
|
+
mapFolding/babbage.py,sha256=3D1qcntoiJm2tqgbiCAMc7GpGA0SZTdGORNEnensyxk,1882
|
|
3
|
+
mapFolding/beDRY.py,sha256=ZrUXCNqnumTQ1XX8RT5gGdRr-a-o8QxEiGrerUxKtYg,12784
|
|
4
|
+
mapFolding/lovelace.py,sha256=qcyGpVEPP3n0r-RNrSUgPRP3yZJfBEfmok7jYMm1OI0,11473
|
|
5
|
+
mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
|
|
6
|
+
mapFolding/startHere.py,sha256=RGdFoJJdrJ_0tmLIKZn1WnHP0NCZwvQG7C2p4EUHOe4,5034
|
|
7
|
+
mapFolding/theSSOT.py,sha256=yRW6aHyJxheL-Znk537LQA6xUPHz6FfoXY-0Ayh3Lsg,2178
|
|
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/hunterNumba.py,sha256=0giUyqAFzP-XKcq3Kz8wIWCK0BVFhjABVJ1s-w4Jhu0,7109
|
|
13
|
+
mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
|
|
14
|
+
mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
|
|
15
|
+
mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
|
|
16
|
+
mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
|
|
17
|
+
mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
|
|
18
|
+
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
|
|
19
|
+
tests/__init__.py,sha256=PGYVr7r23gATgcvZ3Sfph9D_g1MVvhgzMNWXBs_9tmY,52
|
|
20
|
+
tests/conftest.py,sha256=VFYSd7-tHWd-LUKnTY24PIJhq9quP9S3sK2SYusNNog,12875
|
|
21
|
+
tests/test_oeis.py,sha256=vxnwO-cSR68htkyMh9QMVv-lvxBo6qlwPg1Rbx4JylY,7963
|
|
22
|
+
tests/test_other.py,sha256=cf8DbkZxm_DHNq9lkMe7auXye_XspruU9qiNZATkxr4,6930
|
|
23
|
+
tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
|
|
24
|
+
mapFolding-0.2.2.dist-info/METADATA,sha256=BSCXcKZDhAtOWr8A5pZMG8bZnm5YBLf-6uMN9qs8JDk,5914
|
|
25
|
+
mapFolding-0.2.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
26
|
+
mapFolding-0.2.2.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
27
|
+
mapFolding-0.2.2.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
28
|
+
mapFolding-0.2.2.dist-info/RECORD,,
|
tests/conftest.py
CHANGED
|
@@ -3,31 +3,33 @@ Other test modules must not import directly from the package being tested."""
|
|
|
3
3
|
|
|
4
4
|
# TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
|
|
5
5
|
|
|
6
|
-
from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union
|
|
6
|
+
from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
|
|
7
7
|
import pathlib
|
|
8
8
|
import pytest
|
|
9
9
|
import random
|
|
10
|
+
import shutil
|
|
10
11
|
import unittest.mock
|
|
11
|
-
|
|
12
|
-
from
|
|
13
|
-
from
|
|
12
|
+
import uuid
|
|
13
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
|
|
14
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
|
|
15
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
|
|
16
|
+
from mapFolding import countFolds, pathJobDEFAULT, indexMy, indexThe, indexTrack
|
|
17
|
+
from mapFolding import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
14
18
|
from mapFolding import getLeavesTotal, parseDimensions, validateListDimensions
|
|
15
|
-
from mapFolding
|
|
16
|
-
from mapFolding
|
|
17
|
-
from mapFolding.
|
|
19
|
+
from mapFolding import getTaskDivisions, makeConnectionGraph, outfitFoldings, setCPUlimit
|
|
20
|
+
from mapFolding import oeisIDfor_n, getOEISids, clearOEIScache
|
|
21
|
+
from mapFolding.beDRY import makeDataContainer
|
|
18
22
|
from mapFolding.oeis import OEIS_for_n
|
|
19
|
-
from mapFolding.oeis import
|
|
23
|
+
from mapFolding.oeis import _getFilenameOEISbFile
|
|
20
24
|
from mapFolding.oeis import _getOEISidValues
|
|
21
25
|
from mapFolding.oeis import _parseBFileOEIS
|
|
22
26
|
from mapFolding.oeis import _validateOEISid
|
|
23
|
-
from mapFolding.oeis import getOEISids
|
|
24
|
-
from mapFolding.oeis import oeisIDfor_n
|
|
25
27
|
from mapFolding.oeis import oeisIDsImplemented
|
|
26
28
|
from mapFolding.oeis import settingsOEIS
|
|
27
29
|
|
|
28
30
|
__all__ = [
|
|
29
31
|
'OEIS_for_n',
|
|
30
|
-
'
|
|
32
|
+
'_getFilenameOEISbFile',
|
|
31
33
|
'_getOEISidValues',
|
|
32
34
|
'_parseBFileOEIS',
|
|
33
35
|
'_validateOEISid',
|
|
@@ -37,14 +39,20 @@ __all__ = [
|
|
|
37
39
|
'expectSystemExit',
|
|
38
40
|
'getLeavesTotal',
|
|
39
41
|
'getOEISids',
|
|
42
|
+
'indexThe',
|
|
43
|
+
'getTaskDivisions',
|
|
40
44
|
'intInnit',
|
|
45
|
+
'makeConnectionGraph',
|
|
46
|
+
'makeDataContainer',
|
|
41
47
|
'makeTestSuiteConcurrencyLimit',
|
|
42
48
|
'makeTestSuiteIntInnit',
|
|
43
49
|
'makeTestSuiteOopsieKwargsie',
|
|
44
50
|
'oeisIDfor_n',
|
|
45
51
|
'oeisIDsImplemented',
|
|
46
52
|
'oopsieKwargsie',
|
|
53
|
+
'outfitFoldings',
|
|
47
54
|
'parseDimensions',
|
|
55
|
+
'setCPUlimit',
|
|
48
56
|
'settingsOEIS',
|
|
49
57
|
'standardCacheTest',
|
|
50
58
|
'standardComparison',
|
|
@@ -91,6 +99,75 @@ def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
|
|
|
91
99
|
|
|
92
100
|
return dictionaryMapDimensionsToFoldsTotalKnown
|
|
93
101
|
|
|
102
|
+
"""
|
|
103
|
+
Section: temporary paths and pathFilenames"""
|
|
104
|
+
|
|
105
|
+
# SSOT for test data paths
|
|
106
|
+
pathDataSamples = pathlib.Path("tests/dataSamples")
|
|
107
|
+
pathTempRoot = pathDataSamples / "tmp"
|
|
108
|
+
|
|
109
|
+
# The registrar maintains the register of temp files
|
|
110
|
+
registerOfTempFiles: Set[pathlib.Path] = set()
|
|
111
|
+
|
|
112
|
+
def addTempFileToRegister(path: pathlib.Path) -> None:
|
|
113
|
+
"""The registrar adds a temp file to the register."""
|
|
114
|
+
registerOfTempFiles.add(path)
|
|
115
|
+
|
|
116
|
+
def cleanupTempFileRegister() -> None:
|
|
117
|
+
"""The registrar cleans up temp files in the register."""
|
|
118
|
+
for pathTemp in sorted(registerOfTempFiles, reverse=True):
|
|
119
|
+
try:
|
|
120
|
+
if pathTemp.is_file():
|
|
121
|
+
pathTemp.unlink(missing_ok=True)
|
|
122
|
+
elif pathTemp.is_dir():
|
|
123
|
+
shutil.rmtree(pathTemp, ignore_errors=True)
|
|
124
|
+
except Exception as ERRORmessage:
|
|
125
|
+
print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
|
|
126
|
+
registerOfTempFiles.clear()
|
|
127
|
+
|
|
128
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
129
|
+
def setupTeardownTestData() -> Generator[None, None, None]:
|
|
130
|
+
"""Auto-fixture to setup test data directories and cleanup after."""
|
|
131
|
+
pathDataSamples.mkdir(exist_ok=True)
|
|
132
|
+
pathTempRoot.mkdir(exist_ok=True)
|
|
133
|
+
yield
|
|
134
|
+
cleanupTempFileRegister()
|
|
135
|
+
|
|
136
|
+
@pytest.fixture
|
|
137
|
+
def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
|
|
138
|
+
"""Create a unique temp directory for each test function."""
|
|
139
|
+
# Sanitize test name for filesystem compatibility
|
|
140
|
+
sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
|
|
141
|
+
uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
|
|
142
|
+
pathTemp = pathTempRoot / uniqueDirectory
|
|
143
|
+
pathTemp.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
|
|
145
|
+
addTempFileToRegister(pathTemp)
|
|
146
|
+
return pathTemp
|
|
147
|
+
|
|
148
|
+
@pytest.fixture
|
|
149
|
+
def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
150
|
+
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
151
|
+
from mapFolding import oeis as there_must_be_a_better_way
|
|
152
|
+
pathCacheOriginal = there_must_be_a_better_way._pathCache
|
|
153
|
+
there_must_be_a_better_way._pathCache = pathTempTesting
|
|
154
|
+
yield pathTempTesting
|
|
155
|
+
there_must_be_a_better_way._pathCache = pathCacheOriginal
|
|
156
|
+
|
|
157
|
+
@pytest.fixture
|
|
158
|
+
def pathFilenameBenchmarksTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
159
|
+
"""Temporarily replace the benchmarks directory with a test directory."""
|
|
160
|
+
from mapFolding.benchmarks import benchmarking
|
|
161
|
+
pathFilenameOriginal = benchmarking.pathFilenameRecordedBenchmarks
|
|
162
|
+
pathFilenameTest = pathTempTesting / "benchmarks.npy"
|
|
163
|
+
benchmarking.pathFilenameRecordedBenchmarks = pathFilenameTest
|
|
164
|
+
yield pathFilenameTest
|
|
165
|
+
benchmarking.pathFilenameRecordedBenchmarks = pathFilenameOriginal
|
|
166
|
+
|
|
167
|
+
@pytest.fixture
|
|
168
|
+
def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
|
|
169
|
+
return pathTempTesting.joinpath("foldsTotalTest.txt")
|
|
170
|
+
|
|
94
171
|
"""
|
|
95
172
|
Section: Fixtures"""
|
|
96
173
|
|
|
@@ -150,28 +227,15 @@ def oeisID_1random() -> str:
|
|
|
150
227
|
"""Return one random valid OEIS ID."""
|
|
151
228
|
return random.choice(oeisIDsImplemented)
|
|
152
229
|
|
|
153
|
-
@pytest.fixture
|
|
154
|
-
def pathCacheTesting(tmp_path: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
155
|
-
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
156
|
-
from mapFolding import oeis as there_must_be_a_better_way
|
|
157
|
-
pathCacheOriginal = there_must_be_a_better_way._pathCache
|
|
158
|
-
there_must_be_a_better_way._pathCache = tmp_path
|
|
159
|
-
yield tmp_path
|
|
160
|
-
there_must_be_a_better_way._pathCache = pathCacheOriginal
|
|
161
|
-
|
|
162
|
-
@pytest.fixture
|
|
163
|
-
def pathBenchmarksTesting(tmp_path: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
164
|
-
"""Temporarily replace the benchmarks directory with a test directory."""
|
|
165
|
-
from mapFolding.benchmarks import benchmarking
|
|
166
|
-
pathOriginal = benchmarking.pathFilenameRecordedBenchmarks
|
|
167
|
-
pathTest = tmp_path / "benchmarks.npy"
|
|
168
|
-
benchmarking.pathFilenameRecordedBenchmarks = pathTest
|
|
169
|
-
yield pathTest
|
|
170
|
-
benchmarking.pathFilenameRecordedBenchmarks = pathOriginal
|
|
171
|
-
|
|
172
230
|
"""
|
|
173
231
|
Section: Standardized test structures"""
|
|
174
232
|
|
|
233
|
+
def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
234
|
+
"""Format assertion message for any test comparison."""
|
|
235
|
+
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
236
|
+
f"Expected: {expected}\n"
|
|
237
|
+
f"Got: {actual}")
|
|
238
|
+
|
|
175
239
|
def standardComparison(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
|
|
176
240
|
"""Template for tests expecting an error."""
|
|
177
241
|
if type(expected) == Type[Exception]:
|
|
@@ -217,12 +281,6 @@ def expectSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: C
|
|
|
217
281
|
assert exitCode == expected, \
|
|
218
282
|
f"Expected exit code {expected} but got {exitCode}"
|
|
219
283
|
|
|
220
|
-
def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
221
|
-
"""Format assertion message for any test comparison."""
|
|
222
|
-
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
223
|
-
f"Expected: {expected}\n"
|
|
224
|
-
f"Got: {actual}")
|
|
225
|
-
|
|
226
284
|
def standardCacheTest(
|
|
227
285
|
expected: Any,
|
|
228
286
|
setupCacheFile: Optional[Callable[[pathlib.Path, str], None]],
|
|
@@ -237,7 +295,7 @@ def standardCacheTest(
|
|
|
237
295
|
oeisID: OEIS ID to test
|
|
238
296
|
pathCache: Temporary cache directory path
|
|
239
297
|
"""
|
|
240
|
-
pathFilenameCache = pathCache /
|
|
298
|
+
pathFilenameCache = pathCache / _getFilenameOEISbFile(oeisID)
|
|
241
299
|
|
|
242
300
|
# Setup cache file if provided
|
|
243
301
|
if setupCacheFile:
|