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
mapFolding/oeis.py
CHANGED
|
@@ -65,6 +65,40 @@ settingsOEIShardcodedValues = {
|
|
|
65
65
|
oeisIDsImplemented: Final[List[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
|
|
66
66
|
"""Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
|
|
67
67
|
|
|
68
|
+
def _validateOEISid(oeisIDcandidate: str):
|
|
69
|
+
"""
|
|
70
|
+
Validates an OEIS sequence ID against implemented sequences.
|
|
71
|
+
|
|
72
|
+
If the provided ID is recognized within the application's implemented
|
|
73
|
+
OEIS sequences, the function returns the verified ID in uppercase.
|
|
74
|
+
Otherwise, a KeyError is raised indicating that the sequence is not
|
|
75
|
+
directly supported.
|
|
76
|
+
|
|
77
|
+
Parameters:
|
|
78
|
+
oeisIDcandidate: The OEIS sequence identifier to validate.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
oeisID: The validated and possibly modified OEIS sequence ID, if recognized.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
KeyError: If the provided sequence ID is not directly implemented.
|
|
85
|
+
"""
|
|
86
|
+
if oeisIDcandidate in oeisIDsImplemented:
|
|
87
|
+
return oeisIDcandidate
|
|
88
|
+
else:
|
|
89
|
+
oeisIDcleaned = str(oeisIDcandidate).upper().strip()
|
|
90
|
+
if oeisIDcleaned in oeisIDsImplemented:
|
|
91
|
+
return oeisIDcleaned
|
|
92
|
+
else:
|
|
93
|
+
raise KeyError(
|
|
94
|
+
f"OEIS ID {oeisIDcandidate} is not directly implemented.\n"
|
|
95
|
+
f"Available sequences:\n{_formatOEISsequenceInfo()}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _getFilenameOEISbFile(oeisID: str) -> str:
|
|
99
|
+
oeisID = _validateOEISid(oeisID)
|
|
100
|
+
return f"b{oeisID[1:]}.txt"
|
|
101
|
+
|
|
68
102
|
def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
|
|
69
103
|
"""
|
|
70
104
|
Parses the content of an OEIS b-file for a given sequence ID.
|
|
@@ -101,8 +135,6 @@ try:
|
|
|
101
135
|
except NameError:
|
|
102
136
|
_pathCache = pathlib.Path.home() / ".mapFoldingCache"
|
|
103
137
|
|
|
104
|
-
_formatFilenameCache = "{oeisID}.txt"
|
|
105
|
-
|
|
106
138
|
def _getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
107
139
|
"""
|
|
108
140
|
Retrieves the specified OEIS sequence as a dictionary mapping integer indices
|
|
@@ -122,7 +154,7 @@ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
|
122
154
|
IOError: If there is an error reading from or writing to the local cache.
|
|
123
155
|
"""
|
|
124
156
|
|
|
125
|
-
pathFilenameCache = _pathCache /
|
|
157
|
+
pathFilenameCache = _pathCache / _getFilenameOEISbFile(oeisID)
|
|
126
158
|
cacheDays = 7
|
|
127
159
|
|
|
128
160
|
tryCache = False
|
|
@@ -137,7 +169,7 @@ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
|
137
169
|
except (ValueError, IOError):
|
|
138
170
|
tryCache = False
|
|
139
171
|
|
|
140
|
-
urlOEISbFile = f"https://oeis.org/{oeisID}/
|
|
172
|
+
urlOEISbFile = f"https://oeis.org/{oeisID}/{_getFilenameOEISbFile(oeisID)}"
|
|
141
173
|
httpResponse: urllib.response.addinfourl = urllib.request.urlopen(urlOEISbFile)
|
|
142
174
|
OEISbFile = httpResponse.read().decode('utf-8')
|
|
143
175
|
|
|
@@ -212,36 +244,6 @@ def _formatOEISsequenceInfo() -> str:
|
|
|
212
244
|
for oeisID in oeisIDsImplemented
|
|
213
245
|
)
|
|
214
246
|
|
|
215
|
-
def _validateOEISid(oeisIDcandidate: str):
|
|
216
|
-
"""
|
|
217
|
-
Validates an OEIS sequence ID against implemented sequences.
|
|
218
|
-
|
|
219
|
-
If the provided ID is recognized within the application's implemented
|
|
220
|
-
OEIS sequences, the function returns the verified ID in uppercase.
|
|
221
|
-
Otherwise, a KeyError is raised indicating that the sequence is not
|
|
222
|
-
directly supported.
|
|
223
|
-
|
|
224
|
-
Parameters:
|
|
225
|
-
oeisIDcandidate: The OEIS sequence identifier to validate.
|
|
226
|
-
|
|
227
|
-
Returns:
|
|
228
|
-
oeisID: The validated and possibly modified OEIS sequence ID, if recognized.
|
|
229
|
-
|
|
230
|
-
Raises:
|
|
231
|
-
KeyError: If the provided sequence ID is not directly implemented.
|
|
232
|
-
"""
|
|
233
|
-
if oeisIDcandidate in oeisIDsImplemented:
|
|
234
|
-
return oeisIDcandidate
|
|
235
|
-
else:
|
|
236
|
-
oeisIDcleaned = str(oeisIDcandidate).upper().strip()
|
|
237
|
-
if oeisIDcleaned in oeisIDsImplemented:
|
|
238
|
-
return oeisIDcleaned
|
|
239
|
-
else:
|
|
240
|
-
raise KeyError(
|
|
241
|
-
f"OEIS ID {oeisIDcandidate} is not directly implemented.\n"
|
|
242
|
-
f"Available sequences:\n{_formatOEISsequenceInfo()}"
|
|
243
|
-
)
|
|
244
|
-
|
|
245
247
|
"""
|
|
246
248
|
Section: public functions"""
|
|
247
249
|
|
|
@@ -308,7 +310,7 @@ def clearOEIScache() -> None:
|
|
|
308
310
|
return
|
|
309
311
|
else:
|
|
310
312
|
for oeisID in settingsOEIS:
|
|
311
|
-
pathFilenameCache = _pathCache /
|
|
313
|
+
pathFilenameCache = _pathCache / _getFilenameOEISbFile(oeisID)
|
|
312
314
|
pathFilenameCache.unlink(missing_ok=True)
|
|
313
315
|
|
|
314
316
|
print(f"Cache cleared from {_pathCache}")
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""The algorithm flattened into semantic sections.
|
|
2
|
+
This version is not maintained, so you may see differences from the current version."""
|
|
3
|
+
from numpy import integer
|
|
4
|
+
from numpy.typing import NDArray
|
|
5
|
+
from typing import List, Any, Final, Optional, Union, Sequence, Tuple, Type, TypedDict
|
|
6
|
+
import enum
|
|
7
|
+
import numpy
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
def countFolds(listDimensions: Sequence[int], computationDivisions = None, CPUlimit: Optional[Union[int, float, bool]] = None):
|
|
11
|
+
def doWhile():
|
|
12
|
+
|
|
13
|
+
while activeLeafGreaterThan0Condition():
|
|
14
|
+
|
|
15
|
+
if activeLeafIsTheFirstLeafCondition() or leafBelowSentinelIs1Condition():
|
|
16
|
+
|
|
17
|
+
if activeLeafGreaterThanLeavesTotalCondition():
|
|
18
|
+
foldsSubTotalsIncrement()
|
|
19
|
+
|
|
20
|
+
else:
|
|
21
|
+
|
|
22
|
+
findGapsInitializeVariables()
|
|
23
|
+
while loopingTheDimensions():
|
|
24
|
+
|
|
25
|
+
if dimensionsUnconstrainedCondition():
|
|
26
|
+
dimensionsUnconstrainedIncrement()
|
|
27
|
+
|
|
28
|
+
else:
|
|
29
|
+
|
|
30
|
+
leafConnecteeInitialization()
|
|
31
|
+
while loopingLeavesConnectedToActiveLeaf():
|
|
32
|
+
if thereAreComputationDivisionsYouMightSkip():
|
|
33
|
+
countGaps()
|
|
34
|
+
leafConnecteeUpdate()
|
|
35
|
+
|
|
36
|
+
dimension1ndexIncrement()
|
|
37
|
+
|
|
38
|
+
if allDimensionsAreUnconstrained():
|
|
39
|
+
insertUnconstrainedLeaf()
|
|
40
|
+
|
|
41
|
+
indexMiniGapInitialization()
|
|
42
|
+
while loopingToActiveGapCeiling():
|
|
43
|
+
filterCommonGaps()
|
|
44
|
+
indexMiniGapIncrement()
|
|
45
|
+
|
|
46
|
+
while backtrackCondition():
|
|
47
|
+
backtrack()
|
|
48
|
+
|
|
49
|
+
if placeLeafCondition():
|
|
50
|
+
placeLeaf()
|
|
51
|
+
|
|
52
|
+
def activeGapIncrement():
|
|
53
|
+
my[indexMy.gap1ndex] += 1
|
|
54
|
+
|
|
55
|
+
def activeLeafGreaterThan0Condition():
|
|
56
|
+
return my[indexMy.leaf1ndex] > 0
|
|
57
|
+
|
|
58
|
+
def activeLeafGreaterThanLeavesTotalCondition():
|
|
59
|
+
return my[indexMy.leaf1ndex] > the[indexThe.leavesTotal]
|
|
60
|
+
|
|
61
|
+
def activeLeafIsTheFirstLeafCondition():
|
|
62
|
+
return my[indexMy.leaf1ndex] <= 1
|
|
63
|
+
|
|
64
|
+
def activeLeafNotEqualToTaskDivisionsCondition():
|
|
65
|
+
return my[indexMy.leaf1ndex] != the[indexThe.taskDivisions]
|
|
66
|
+
|
|
67
|
+
def allDimensionsAreUnconstrained():
|
|
68
|
+
return my[indexMy.dimensionsUnconstrained] == the[indexThe.dimensionsTotal]
|
|
69
|
+
|
|
70
|
+
def backtrack():
|
|
71
|
+
my[indexMy.leaf1ndex] -= 1
|
|
72
|
+
track[indexTrack.leafBelow, track[indexTrack.leafAbove, my[indexMy.leaf1ndex]]] = track[indexTrack.leafBelow, my[indexMy.leaf1ndex]]
|
|
73
|
+
track[indexTrack.leafAbove, track[indexTrack.leafBelow, my[indexMy.leaf1ndex]]] = track[indexTrack.leafAbove, my[indexMy.leaf1ndex]]
|
|
74
|
+
|
|
75
|
+
def backtrackCondition():
|
|
76
|
+
return my[indexMy.leaf1ndex] > 0 and my[indexMy.gap1ndex] == track[indexTrack.gapRangeStart, my[indexMy.leaf1ndex] - 1]
|
|
77
|
+
|
|
78
|
+
def computationDivisionsCondition():
|
|
79
|
+
return the[indexThe.taskDivisions] == int(False)
|
|
80
|
+
|
|
81
|
+
def countGaps():
|
|
82
|
+
gapsWhere[my[indexMy.gap1ndexCeiling]] = my[indexMy.leafConnectee]
|
|
83
|
+
if track[indexTrack.countDimensionsGapped, my[indexMy.leafConnectee]] == 0:
|
|
84
|
+
gap1ndexCeilingIncrement()
|
|
85
|
+
track[indexTrack.countDimensionsGapped, my[indexMy.leafConnectee]] += 1
|
|
86
|
+
|
|
87
|
+
def dimension1ndexIncrement():
|
|
88
|
+
my[indexMy.dimension1ndex] += 1
|
|
89
|
+
|
|
90
|
+
def dimensionsUnconstrainedCondition():
|
|
91
|
+
return connectionGraph[my[indexMy.dimension1ndex], my[indexMy.leaf1ndex], my[indexMy.leaf1ndex]] == my[indexMy.leaf1ndex]
|
|
92
|
+
|
|
93
|
+
def dimensionsUnconstrainedIncrement():
|
|
94
|
+
my[indexMy.dimensionsUnconstrained] += 1
|
|
95
|
+
|
|
96
|
+
def filterCommonGaps():
|
|
97
|
+
gapsWhere[my[indexMy.gap1ndex]] = gapsWhere[my[indexMy.indexMiniGap]]
|
|
98
|
+
if track[indexTrack.countDimensionsGapped, gapsWhere[my[indexMy.indexMiniGap]]] == the[indexThe.dimensionsTotal] - my[indexMy.dimensionsUnconstrained]:
|
|
99
|
+
activeGapIncrement()
|
|
100
|
+
track[indexTrack.countDimensionsGapped, gapsWhere[my[indexMy.indexMiniGap]]] = 0
|
|
101
|
+
|
|
102
|
+
def findGapsInitializeVariables():
|
|
103
|
+
my[indexMy.dimensionsUnconstrained] = 0
|
|
104
|
+
my[indexMy.gap1ndexCeiling] = track[indexTrack.gapRangeStart, my[indexMy.leaf1ndex] - 1]
|
|
105
|
+
my[indexMy.dimension1ndex] = 1
|
|
106
|
+
|
|
107
|
+
def foldsSubTotalsIncrement():
|
|
108
|
+
foldsSubTotals[my[indexMy.taskIndex]] += the[indexThe.leavesTotal]
|
|
109
|
+
|
|
110
|
+
def gap1ndexCeilingIncrement():
|
|
111
|
+
my[indexMy.gap1ndexCeiling] += 1
|
|
112
|
+
|
|
113
|
+
def indexMiniGapIncrement():
|
|
114
|
+
my[indexMy.indexMiniGap] += 1
|
|
115
|
+
|
|
116
|
+
def indexMiniGapInitialization():
|
|
117
|
+
my[indexMy.indexMiniGap] = my[indexMy.gap1ndex]
|
|
118
|
+
|
|
119
|
+
def insertUnconstrainedLeaf():
|
|
120
|
+
my[indexMy.indexLeaf] = 0
|
|
121
|
+
while my[indexMy.indexLeaf] < my[indexMy.leaf1ndex]:
|
|
122
|
+
gapsWhere[my[indexMy.gap1ndexCeiling]] = my[indexMy.indexLeaf]
|
|
123
|
+
my[indexMy.gap1ndexCeiling] += 1
|
|
124
|
+
my[indexMy.indexLeaf] += 1
|
|
125
|
+
|
|
126
|
+
def leafBelowSentinelIs1Condition():
|
|
127
|
+
return track[indexTrack.leafBelow, 0] == 1
|
|
128
|
+
|
|
129
|
+
def leafConnecteeInitialization():
|
|
130
|
+
my[indexMy.leafConnectee] = connectionGraph[my[indexMy.dimension1ndex], my[indexMy.leaf1ndex], my[indexMy.leaf1ndex]]
|
|
131
|
+
|
|
132
|
+
def leafConnecteeUpdate():
|
|
133
|
+
my[indexMy.leafConnectee] = connectionGraph[my[indexMy.dimension1ndex], my[indexMy.leaf1ndex], track[indexTrack.leafBelow, my[indexMy.leafConnectee]]]
|
|
134
|
+
|
|
135
|
+
def loopingLeavesConnectedToActiveLeaf():
|
|
136
|
+
return my[indexMy.leafConnectee] != my[indexMy.leaf1ndex]
|
|
137
|
+
|
|
138
|
+
def loopingTheDimensions():
|
|
139
|
+
return my[indexMy.dimension1ndex] <= the[indexThe.dimensionsTotal]
|
|
140
|
+
|
|
141
|
+
def loopingToActiveGapCeiling():
|
|
142
|
+
return my[indexMy.indexMiniGap] < my[indexMy.gap1ndexCeiling]
|
|
143
|
+
|
|
144
|
+
def placeLeaf():
|
|
145
|
+
my[indexMy.gap1ndex] -= 1
|
|
146
|
+
track[indexTrack.leafAbove, my[indexMy.leaf1ndex]] = gapsWhere[my[indexMy.gap1ndex]]
|
|
147
|
+
track[indexTrack.leafBelow, my[indexMy.leaf1ndex]] = track[indexTrack.leafBelow, track[indexTrack.leafAbove, my[indexMy.leaf1ndex]]]
|
|
148
|
+
track[indexTrack.leafBelow, track[indexTrack.leafAbove, my[indexMy.leaf1ndex]]] = my[indexMy.leaf1ndex]
|
|
149
|
+
track[indexTrack.leafAbove, track[indexTrack.leafBelow, my[indexMy.leaf1ndex]]] = my[indexMy.leaf1ndex]
|
|
150
|
+
track[indexTrack.gapRangeStart, my[indexMy.leaf1ndex]] = my[indexMy.gap1ndex]
|
|
151
|
+
my[indexMy.leaf1ndex] += 1
|
|
152
|
+
|
|
153
|
+
def placeLeafCondition():
|
|
154
|
+
return my[indexMy.leaf1ndex] > 0
|
|
155
|
+
|
|
156
|
+
def taskIndexCondition():
|
|
157
|
+
return my[indexMy.leafConnectee] % the[indexThe.taskDivisions] == my[indexMy.taskIndex]
|
|
158
|
+
|
|
159
|
+
def thereAreComputationDivisionsYouMightSkip():
|
|
160
|
+
if computationDivisionsCondition():
|
|
161
|
+
return True
|
|
162
|
+
if activeLeafNotEqualToTaskDivisionsCondition():
|
|
163
|
+
return True
|
|
164
|
+
if taskIndexCondition():
|
|
165
|
+
return True
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
stateUniversal = outfitFoldings(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit)
|
|
169
|
+
connectionGraph: Final[numpy.ndarray] = stateUniversal['connectionGraph']
|
|
170
|
+
foldsSubTotals = stateUniversal['foldsSubTotals']
|
|
171
|
+
gapsWhere = stateUniversal['gapsWhere']
|
|
172
|
+
my = stateUniversal['my']
|
|
173
|
+
the: Final[numpy.ndarray] = stateUniversal['the']
|
|
174
|
+
track = stateUniversal['track']
|
|
175
|
+
|
|
176
|
+
if the[indexThe.taskDivisions] == int(False):
|
|
177
|
+
doWhile()
|
|
178
|
+
else:
|
|
179
|
+
stateUniversal['my'] = my.copy()
|
|
180
|
+
stateUniversal['gapsWhere'] = gapsWhere.copy()
|
|
181
|
+
stateUniversal['track'] = track.copy()
|
|
182
|
+
for indexSherpa in range(the[indexThe.taskDivisions]):
|
|
183
|
+
my = stateUniversal['my'].copy()
|
|
184
|
+
my[indexMy.taskIndex] = indexSherpa
|
|
185
|
+
gapsWhere = stateUniversal['gapsWhere'].copy()
|
|
186
|
+
track = stateUniversal['track'].copy()
|
|
187
|
+
doWhile()
|
|
188
|
+
|
|
189
|
+
return numpy.sum(foldsSubTotals).item()
|
|
190
|
+
|
|
191
|
+
@enum.verify(enum.CONTINUOUS, enum.UNIQUE) if sys.version_info >= (3, 11) else lambda x: x
|
|
192
|
+
class EnumIndices(enum.IntEnum):
|
|
193
|
+
"""Base class for index enums."""
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _generate_next_value_(name, start, count, last_values):
|
|
196
|
+
"""0-indexed."""
|
|
197
|
+
return count
|
|
198
|
+
|
|
199
|
+
def __index__(self) -> int:
|
|
200
|
+
"""Adapt enum to the ultra-rare event of indexing a NumPy 'ndarray', which is not the
|
|
201
|
+
same as `array.array`. See NumPy.org; I think it will be very popular someday."""
|
|
202
|
+
return self
|
|
203
|
+
|
|
204
|
+
class indexMy(EnumIndices):
|
|
205
|
+
"""Indices for dynamic values."""
|
|
206
|
+
dimension1ndex = enum.auto()
|
|
207
|
+
dimensionsUnconstrained = enum.auto()
|
|
208
|
+
gap1ndex = enum.auto()
|
|
209
|
+
gap1ndexCeiling = enum.auto()
|
|
210
|
+
indexLeaf = enum.auto()
|
|
211
|
+
indexMiniGap = enum.auto()
|
|
212
|
+
leaf1ndex = enum.auto()
|
|
213
|
+
leafConnectee = enum.auto()
|
|
214
|
+
taskIndex = enum.auto()
|
|
215
|
+
|
|
216
|
+
class indexThe(EnumIndices):
|
|
217
|
+
"""Indices for static values."""
|
|
218
|
+
dimensionsTotal = enum.auto()
|
|
219
|
+
leavesTotal = enum.auto()
|
|
220
|
+
taskDivisions = enum.auto()
|
|
221
|
+
|
|
222
|
+
class indexTrack(EnumIndices):
|
|
223
|
+
"""Indices for state tracking array."""
|
|
224
|
+
leafAbove = enum.auto()
|
|
225
|
+
leafBelow = enum.auto()
|
|
226
|
+
countDimensionsGapped = enum.auto()
|
|
227
|
+
gapRangeStart = enum.auto()
|
|
228
|
+
|
|
229
|
+
class computationState(TypedDict):
|
|
230
|
+
connectionGraph: NDArray[integer[Any]]
|
|
231
|
+
foldsSubTotals: NDArray[integer[Any]]
|
|
232
|
+
mapShape: Tuple[int, ...]
|
|
233
|
+
my: NDArray[integer[Any]]
|
|
234
|
+
gapsWhere: NDArray[integer[Any]]
|
|
235
|
+
the: NDArray[integer[Any]]
|
|
236
|
+
track: NDArray[integer[Any]]
|
|
237
|
+
|
|
238
|
+
dtypeLarge = numpy.int64
|
|
239
|
+
dtypeDefault = dtypeLarge
|
|
240
|
+
|
|
241
|
+
def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
242
|
+
"""
|
|
243
|
+
How many leaves are in the map.
|
|
244
|
+
|
|
245
|
+
Parameters:
|
|
246
|
+
listDimensions: A list of integers representing dimensions.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
productDimensions: The product of all positive integer dimensions.
|
|
250
|
+
"""
|
|
251
|
+
listNonNegative = parseDimensions(listDimensions, 'listDimensions')
|
|
252
|
+
listPositive = [dimension for dimension in listNonNegative if dimension > 0]
|
|
253
|
+
|
|
254
|
+
if not listPositive:
|
|
255
|
+
return 0
|
|
256
|
+
else:
|
|
257
|
+
productDimensions = 1
|
|
258
|
+
for dimension in listPositive:
|
|
259
|
+
if dimension > sys.maxsize // productDimensions:
|
|
260
|
+
raise OverflowError(f"I received {dimension=} in {listDimensions=}, but the product of the dimensions exceeds the maximum size of an integer on this system.")
|
|
261
|
+
productDimensions *= dimension
|
|
262
|
+
|
|
263
|
+
return productDimensions
|
|
264
|
+
|
|
265
|
+
def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrencyLimit: int, CPUlimit: Optional[Union[bool, float, int]], listDimensions: Sequence[int]):
|
|
266
|
+
if not computationDivisions:
|
|
267
|
+
return 0
|
|
268
|
+
else:
|
|
269
|
+
leavesTotal = getLeavesTotal(listDimensions)
|
|
270
|
+
if isinstance(computationDivisions, int):
|
|
271
|
+
taskDivisions = computationDivisions
|
|
272
|
+
elif isinstance(computationDivisions, str):
|
|
273
|
+
computationDivisions = computationDivisions.lower()
|
|
274
|
+
if computationDivisions == "maximum":
|
|
275
|
+
taskDivisions = leavesTotal
|
|
276
|
+
elif computationDivisions == "cpu":
|
|
277
|
+
taskDivisions = min(concurrencyLimit, leavesTotal)
|
|
278
|
+
else:
|
|
279
|
+
raise ValueError("Not my problem.")
|
|
280
|
+
|
|
281
|
+
if taskDivisions > leavesTotal:
|
|
282
|
+
raise ValueError("What are you doing?")
|
|
283
|
+
|
|
284
|
+
return taskDivisions
|
|
285
|
+
|
|
286
|
+
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optional[Type]) -> NDArray[integer[Any]]:
|
|
287
|
+
datatype = keywordArguments.get('datatype', dtypeDefault)
|
|
288
|
+
mapShape = validateListDimensions(listDimensions)
|
|
289
|
+
leavesTotal = getLeavesTotal(mapShape)
|
|
290
|
+
arrayDimensions = numpy.array(mapShape, dtype=datatype)
|
|
291
|
+
dimensionsTotal = len(arrayDimensions)
|
|
292
|
+
|
|
293
|
+
cumulativeProduct = numpy.multiply.accumulate([1] + mapShape, dtype=datatype)
|
|
294
|
+
coordinateSystem = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1), dtype=datatype)
|
|
295
|
+
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
296
|
+
for leaf1ndex in range(1, leavesTotal + 1):
|
|
297
|
+
coordinateSystem[dimension1ndex, leaf1ndex] = ( ((leaf1ndex - 1) // cumulativeProduct[dimension1ndex - 1]) % arrayDimensions[dimension1ndex - 1] + 1 )
|
|
298
|
+
|
|
299
|
+
connectionGraph = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1), dtype=datatype)
|
|
300
|
+
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
301
|
+
for activeLeaf1ndex in range(1, leavesTotal + 1):
|
|
302
|
+
for connectee1ndex in range(1, activeLeaf1ndex + 1):
|
|
303
|
+
isFirstCoord = coordinateSystem[dimension1ndex, connectee1ndex] == 1
|
|
304
|
+
isLastCoord = coordinateSystem[dimension1ndex, connectee1ndex] == arrayDimensions[dimension1ndex - 1]
|
|
305
|
+
exceedsActive = connectee1ndex + cumulativeProduct[dimension1ndex - 1] > activeLeaf1ndex
|
|
306
|
+
isEvenParity = (coordinateSystem[dimension1ndex, activeLeaf1ndex] & 1) == (coordinateSystem[dimension1ndex, connectee1ndex] & 1)
|
|
307
|
+
|
|
308
|
+
if (isEvenParity and isFirstCoord) or (not isEvenParity and (isLastCoord or exceedsActive)):
|
|
309
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
310
|
+
elif isEvenParity and not isFirstCoord:
|
|
311
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex - cumulativeProduct[dimension1ndex - 1]
|
|
312
|
+
elif not isEvenParity and not (isLastCoord or exceedsActive):
|
|
313
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex + cumulativeProduct[dimension1ndex - 1]
|
|
314
|
+
else:
|
|
315
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
316
|
+
return connectionGraph
|
|
317
|
+
|
|
318
|
+
def makeDataContainer(shape, datatype: Optional[Type] = None):
|
|
319
|
+
if datatype is None:
|
|
320
|
+
datatype = dtypeDefault
|
|
321
|
+
return numpy.zeros(shape, dtype=datatype)
|
|
322
|
+
|
|
323
|
+
def outfitFoldings(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Type]) -> computationState:
|
|
324
|
+
datatypeDefault = keywordArguments.get('datatypeDefault', dtypeDefault)
|
|
325
|
+
datatypeLarge = keywordArguments.get('datatypeLarge', dtypeLarge)
|
|
326
|
+
|
|
327
|
+
the = makeDataContainer(len(indexThe), datatypeDefault)
|
|
328
|
+
|
|
329
|
+
mapShape = tuple(sorted(validateListDimensions(listDimensions)))
|
|
330
|
+
the[indexThe.leavesTotal] = getLeavesTotal(mapShape)
|
|
331
|
+
the[indexThe.dimensionsTotal] = len(mapShape)
|
|
332
|
+
concurrencyLimit = setCPUlimit(CPUlimit)
|
|
333
|
+
the[indexThe.taskDivisions] = getTaskDivisions(computationDivisions, concurrencyLimit, CPUlimit, listDimensions)
|
|
334
|
+
|
|
335
|
+
stateInitialized = computationState(
|
|
336
|
+
connectionGraph = makeConnectionGraph(mapShape, datatype=datatypeDefault),
|
|
337
|
+
foldsSubTotals = makeDataContainer(the[indexThe.leavesTotal], datatypeLarge),
|
|
338
|
+
mapShape = mapShape,
|
|
339
|
+
my = makeDataContainer(len(indexMy), datatypeLarge),
|
|
340
|
+
gapsWhere = makeDataContainer(int(the[indexThe.leavesTotal]) * int(the[indexThe.leavesTotal]) + 1, datatypeDefault),
|
|
341
|
+
the = the,
|
|
342
|
+
track = makeDataContainer((len(indexTrack), the[indexThe.leavesTotal] + 1), datatypeLarge)
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
stateInitialized['my'][indexMy.leaf1ndex] = 1
|
|
346
|
+
return stateInitialized
|
|
347
|
+
|
|
348
|
+
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'unnamed parameter') -> List[int]:
|
|
349
|
+
# listValidated = intInnit(dimensions, parameterName)
|
|
350
|
+
listNOTValidated = dimensions if isinstance(dimensions, (list, tuple)) else list(dimensions)
|
|
351
|
+
listNonNegative = []
|
|
352
|
+
for dimension in listNOTValidated:
|
|
353
|
+
if dimension < 0:
|
|
354
|
+
raise ValueError(f"Dimension {dimension} must be non-negative")
|
|
355
|
+
listNonNegative.append(dimension)
|
|
356
|
+
if not listNonNegative:
|
|
357
|
+
raise ValueError("At least one dimension must be non-negative")
|
|
358
|
+
return listNonNegative
|
|
359
|
+
|
|
360
|
+
def setCPUlimit(CPUlimit: Union[bool, float, int, None]) -> int:
|
|
361
|
+
# if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
362
|
+
# CPUlimit = oopsieKwargsie(CPUlimit)
|
|
363
|
+
# concurrencyLimit = defineConcurrencyLimit(CPUlimit)
|
|
364
|
+
# numba.set_num_threads(concurrencyLimit)
|
|
365
|
+
concurrencyLimitHARDCODED = 1
|
|
366
|
+
concurrencyLimit = concurrencyLimitHARDCODED
|
|
367
|
+
return concurrencyLimit
|
|
368
|
+
|
|
369
|
+
def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
|
|
370
|
+
if not listDimensions:
|
|
371
|
+
raise ValueError(f"listDimensions is a required parameter.")
|
|
372
|
+
listNonNegative = parseDimensions(listDimensions, 'listDimensions')
|
|
373
|
+
dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
|
|
374
|
+
if len(dimensionsValid) < 2:
|
|
375
|
+
raise NotImplementedError(f"This function requires listDimensions, {listDimensions}, to have at least two dimensions greater than 0. You may want to look at https://oeis.org/.")
|
|
376
|
+
return sorted(dimensionsValid)
|
|
@@ -46,15 +46,15 @@ def countFolds(listDimensions: List[int]) -> int:
|
|
|
46
46
|
|
|
47
47
|
"""Step for... for... for...: fill the connection graph"""
|
|
48
48
|
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
49
|
-
for
|
|
50
|
-
for
|
|
51
|
-
connectionGraph[dimension1ndex,
|
|
52
|
-
else ((
|
|
53
|
-
else
|
|
54
|
-
if (coordinateSystem[dimension1ndex,
|
|
55
|
-
else (
|
|
56
|
-
or
|
|
57
|
-
else
|
|
49
|
+
for leaf1ndex in range(1, leavesTotal + 1):
|
|
50
|
+
for leafConnectee in range(1, leaf1ndex + 1):
|
|
51
|
+
connectionGraph[dimension1ndex, leaf1ndex, leafConnectee] = (0 if leafConnectee == 0
|
|
52
|
+
else ((leafConnectee if coordinateSystem[dimension1ndex, leafConnectee] == 1
|
|
53
|
+
else leafConnectee - cumulativeProduct[dimension1ndex - 1])
|
|
54
|
+
if (coordinateSystem[dimension1ndex, leaf1ndex] & 1) == (coordinateSystem[dimension1ndex, leafConnectee] & 1)
|
|
55
|
+
else (leafConnectee if coordinateSystem[dimension1ndex, leafConnectee] == listDimensions[dimension1ndex-1]
|
|
56
|
+
or leafConnectee + cumulativeProduct[dimension1ndex - 1] > leaf1ndex
|
|
57
|
+
else leafConnectee + cumulativeProduct[dimension1ndex - 1])))
|
|
58
58
|
|
|
59
59
|
"""Indices of array `track` (to "track" the execution state), which is a collection of one-dimensional arrays each of length `leavesTotal + 1`."""
|
|
60
60
|
leafAbove = numba.literally(0)
|
|
@@ -66,67 +66,67 @@ def countFolds(listDimensions: List[int]) -> int:
|
|
|
66
66
|
gapsWhere = numpy.zeros(integerLarge(integerLarge(leavesTotal) * integerLarge(leavesTotal) + 1), dtype=dtypeMaximum)
|
|
67
67
|
|
|
68
68
|
foldsTotal = integerLarge(0)
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
leaf1ndex = integerSmall(1)
|
|
70
|
+
gap1ndex = integerSmall(0)
|
|
71
71
|
|
|
72
|
-
while
|
|
73
|
-
if
|
|
74
|
-
if
|
|
72
|
+
while leaf1ndex > 0:
|
|
73
|
+
if leaf1ndex <= 1 or track[leafBelow, 0] == 1:
|
|
74
|
+
if leaf1ndex > leavesTotal:
|
|
75
75
|
foldsTotal += leavesTotal
|
|
76
76
|
else:
|
|
77
77
|
dimensionsUnconstrained = integerSmall(0)
|
|
78
|
-
"""Track possible gaps for
|
|
79
|
-
gap1ndexCeiling = track[gapRangeStart,
|
|
78
|
+
"""Track possible gaps for leaf1ndex in each section"""
|
|
79
|
+
gap1ndexCeiling = track[gapRangeStart, leaf1ndex - 1]
|
|
80
80
|
|
|
81
|
-
"""Count possible gaps for
|
|
81
|
+
"""Count possible gaps for leaf1ndex in each section"""
|
|
82
82
|
dimension1ndex = integerSmall(1)
|
|
83
83
|
while dimension1ndex <= dimensionsTotal:
|
|
84
|
-
if connectionGraph[dimension1ndex,
|
|
84
|
+
if connectionGraph[dimension1ndex, leaf1ndex, leaf1ndex] == leaf1ndex:
|
|
85
85
|
dimensionsUnconstrained += 1
|
|
86
86
|
else:
|
|
87
|
-
|
|
88
|
-
while
|
|
89
|
-
gapsWhere[gap1ndexCeiling] =
|
|
90
|
-
if track[countDimensionsGapped,
|
|
87
|
+
leafConnectee = connectionGraph[dimension1ndex, leaf1ndex, leaf1ndex]
|
|
88
|
+
while leafConnectee != leaf1ndex:
|
|
89
|
+
gapsWhere[gap1ndexCeiling] = leafConnectee
|
|
90
|
+
if track[countDimensionsGapped, leafConnectee] == 0:
|
|
91
91
|
gap1ndexCeiling += 1
|
|
92
|
-
track[countDimensionsGapped,
|
|
93
|
-
|
|
92
|
+
track[countDimensionsGapped, leafConnectee] += 1
|
|
93
|
+
leafConnectee = connectionGraph[dimension1ndex, leaf1ndex, track[leafBelow, leafConnectee]]
|
|
94
94
|
dimension1ndex += 1
|
|
95
95
|
|
|
96
|
-
"""If
|
|
96
|
+
"""If leaf1ndex is unconstrained in all sections, it can be inserted anywhere"""
|
|
97
97
|
if dimensionsUnconstrained == dimensionsTotal:
|
|
98
|
-
|
|
99
|
-
while
|
|
100
|
-
gapsWhere[gap1ndexCeiling] =
|
|
98
|
+
indexLeaf = integerSmall(0)
|
|
99
|
+
while indexLeaf < leaf1ndex:
|
|
100
|
+
gapsWhere[gap1ndexCeiling] = indexLeaf
|
|
101
101
|
gap1ndexCeiling += 1
|
|
102
|
-
|
|
102
|
+
indexLeaf += 1
|
|
103
103
|
|
|
104
104
|
"""Filter gaps that are common to all sections"""
|
|
105
|
-
indexMiniGap =
|
|
105
|
+
indexMiniGap = gap1ndex
|
|
106
106
|
while indexMiniGap < gap1ndexCeiling:
|
|
107
|
-
gapsWhere[
|
|
107
|
+
gapsWhere[gap1ndex] = gapsWhere[indexMiniGap]
|
|
108
108
|
if track[countDimensionsGapped, gapsWhere[indexMiniGap]] == dimensionsTotal - dimensionsUnconstrained:
|
|
109
|
-
|
|
109
|
+
gap1ndex += 1
|
|
110
110
|
"""Reset track[countDimensionsGapped] for next iteration"""
|
|
111
111
|
track[countDimensionsGapped, gapsWhere[indexMiniGap]] = 0
|
|
112
112
|
indexMiniGap += 1
|
|
113
113
|
|
|
114
114
|
"""Recursive backtracking steps"""
|
|
115
|
-
while
|
|
116
|
-
|
|
117
|
-
track[leafBelow, track[leafAbove,
|
|
118
|
-
track[leafAbove, track[leafBelow,
|
|
115
|
+
while leaf1ndex > 0 and gap1ndex == track[gapRangeStart, leaf1ndex - 1]:
|
|
116
|
+
leaf1ndex -= 1
|
|
117
|
+
track[leafBelow, track[leafAbove, leaf1ndex]] = track[leafBelow, leaf1ndex]
|
|
118
|
+
track[leafAbove, track[leafBelow, leaf1ndex]] = track[leafAbove, leaf1ndex]
|
|
119
119
|
|
|
120
120
|
"""Place leaf in valid position"""
|
|
121
|
-
if
|
|
122
|
-
|
|
123
|
-
track[leafAbove,
|
|
124
|
-
track[leafBelow,
|
|
125
|
-
track[leafBelow, track[leafAbove,
|
|
126
|
-
track[leafAbove, track[leafBelow,
|
|
121
|
+
if leaf1ndex > 0:
|
|
122
|
+
gap1ndex -= 1
|
|
123
|
+
track[leafAbove, leaf1ndex] = gapsWhere[gap1ndex]
|
|
124
|
+
track[leafBelow, leaf1ndex] = track[leafBelow, track[leafAbove, leaf1ndex]]
|
|
125
|
+
track[leafBelow, track[leafAbove, leaf1ndex]] = leaf1ndex
|
|
126
|
+
track[leafAbove, track[leafBelow, leaf1ndex]] = leaf1ndex
|
|
127
127
|
"""Save current gap index"""
|
|
128
|
-
track[gapRangeStart,
|
|
128
|
+
track[gapRangeStart, leaf1ndex] = gap1ndex
|
|
129
129
|
"""Move to next leaf"""
|
|
130
|
-
|
|
130
|
+
leaf1ndex += 1
|
|
131
131
|
|
|
132
132
|
return int(foldsTotal)
|
mapFolding/reference/lunnan.py
CHANGED
|
@@ -9,11 +9,11 @@ def foldings(p, job=None):
|
|
|
9
9
|
p.insert(0, None) # NOTE mimics Atlas `array` type
|
|
10
10
|
|
|
11
11
|
if job is None:
|
|
12
|
-
global
|
|
13
|
-
|
|
12
|
+
global G
|
|
13
|
+
G = 0
|
|
14
14
|
def job(A, B):
|
|
15
|
-
global
|
|
16
|
-
|
|
15
|
+
global G
|
|
16
|
+
G = G + 1
|
|
17
17
|
return foldings(p, job)
|
|
18
18
|
# perform job (A, B) on each folding of a p[1] x ... x p[d] map,
|
|
19
19
|
# where A and B are the above and below vectors. p[d + 1] < 0 terminates p;
|
|
@@ -150,4 +150,4 @@ def foldings(p, job=None):
|
|
|
150
150
|
else:
|
|
151
151
|
break
|
|
152
152
|
|
|
153
|
-
return
|
|
153
|
+
return G #if job.__closure__ else None
|
|
@@ -13,7 +13,7 @@ def foldings(p: List[int]) -> int:
|
|
|
13
13
|
p: A list of integers representing the dimensions of the map.
|
|
14
14
|
|
|
15
15
|
Returns:
|
|
16
|
-
|
|
16
|
+
G: The number of distinct foldings for the given map dimensions.
|
|
17
17
|
|
|
18
18
|
NOTE If there are fewer than two dimensions, any dimensions are not positive, or any dimensions are not integers, the output will be unreliable.
|
|
19
19
|
"""
|
|
@@ -65,14 +65,14 @@ def foldings(p: List[int]) -> int:
|
|
|
65
65
|
# P[i] = p[1] x ... x p[i], C[i][m] = i-th co-ordinate of leaf m,
|
|
66
66
|
# D[i][l][m] = leaf connected to m in section i when inserting l;
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
G: int = 0
|
|
69
69
|
l: int = 1
|
|
70
70
|
|
|
71
71
|
# kick off with null folding
|
|
72
72
|
while l > 0:
|
|
73
73
|
if l <= 1 or B[0] == 1: # NOTE This statement is part of a significant divergence from the 1971 paper. As a result, this version is greater than one order of magnitude faster.
|
|
74
74
|
if l > n:
|
|
75
|
-
|
|
75
|
+
G = G + n # NOTE Due to `B[0] == 1`, this implementation increments the counted foldings in batches of `n`-many foldings, rather than immediately incrementing when a folding is found, i.e. `G = G + 1`
|
|
76
76
|
else:
|
|
77
77
|
dd: int = 0
|
|
78
78
|
gg: int = gapter[l - 1]
|
|
@@ -120,4 +120,4 @@ def foldings(p: List[int]) -> int:
|
|
|
120
120
|
A[B[l]] = l
|
|
121
121
|
gapter[l] = g
|
|
122
122
|
l = l + 1
|
|
123
|
-
return
|
|
123
|
+
return G
|