mapFolding 0.4.2__py3-none-any.whl → 0.5.0__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 +3 -2
- mapFolding/basecamp.py +12 -14
- mapFolding/beDRY.py +81 -58
- mapFolding/oeis.py +35 -33
- mapFolding/someAssemblyRequired/makeJob.py +8 -7
- mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +1 -3
- mapFolding/someAssemblyRequired/synthesizeNumba.py +57 -60
- mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +102 -30
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +18 -36
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +77 -31
- mapFolding/syntheticModules/numbaCount.py +158 -0
- mapFolding/syntheticModules/numba_doTheNeedful.py +5 -12
- mapFolding/theDao.py +105 -105
- mapFolding/theSSOT.py +80 -205
- mapFolding/theSSOTdatatypes.py +166 -0
- {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/METADATA +2 -1
- mapFolding-0.5.0.dist-info/RECORD +39 -0
- tests/conftest.py +84 -26
- tests/test_computations.py +29 -66
- tests/test_oeis.py +8 -12
- tests/test_other.py +11 -7
- tests/test_tasks.py +5 -5
- mapFolding/syntheticModules/numba_countInitialize.py +0 -52
- mapFolding/syntheticModules/numba_countParallel.py +0 -65
- mapFolding/syntheticModules/numba_countSequential.py +0 -67
- mapFolding/theSSOTnumba.py +0 -125
- mapFolding-0.4.2.dist-info/RECORD +0 -42
- tests/test_types.py +0 -5
- {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/LICENSE +0 -0
- {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/WHEEL +0 -0
- {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py
CHANGED
|
@@ -11,6 +11,7 @@ from mapFolding.theSSOT import (
|
|
|
11
11
|
|
|
12
12
|
# Datatype management
|
|
13
13
|
from mapFolding.theSSOT import (
|
|
14
|
+
getDatatypeModule,
|
|
14
15
|
hackSSOTdatatype,
|
|
15
16
|
hackSSOTdtype,
|
|
16
17
|
setDatatypeElephino,
|
|
@@ -21,7 +22,7 @@ from mapFolding.theSSOT import (
|
|
|
21
22
|
|
|
22
23
|
# Synthesize modules
|
|
23
24
|
from mapFolding.theSSOT import (
|
|
24
|
-
|
|
25
|
+
formatFilenameModuleDEFAULT,
|
|
25
26
|
getAlgorithmDispatcher,
|
|
26
27
|
getAlgorithmSource,
|
|
27
28
|
getPathJobRootDEFAULT,
|
|
@@ -60,7 +61,7 @@ from mapFolding.beDRY import (
|
|
|
60
61
|
from mapFolding.basecamp import countFolds
|
|
61
62
|
from mapFolding.oeis import clearOEIScache, getOEISids, oeisIDfor_n
|
|
62
63
|
|
|
63
|
-
__all__ = [
|
|
64
|
+
__all__: list[str] = [
|
|
64
65
|
'clearOEIScache',
|
|
65
66
|
'countFolds',
|
|
66
67
|
'getOEISids',
|
mapFolding/basecamp.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from mapFolding import computationState, getDispatcherCallable, getPathFilenameFoldsTotal, outfitCountFolds, saveFoldsTotal
|
|
2
|
-
from
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
def countFolds(listDimensions: Sequence[int]
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def countFolds(listDimensions: Sequence[int],
|
|
7
|
+
pathLikeWriteFoldsTotal: str | PathLike[str] | None = None,
|
|
8
|
+
computationDivisions: int | str | None = None,
|
|
9
|
+
CPUlimit: int | float | bool | None = None,
|
|
10
|
+
**keywordArguments: str | bool
|
|
10
11
|
) -> int:
|
|
11
12
|
"""Count the total number of possible foldings for a given map dimensions.
|
|
12
13
|
|
|
@@ -17,7 +18,7 @@ def countFolds(listDimensions: Sequence[int]
|
|
|
17
18
|
computationDivisions (None):
|
|
18
19
|
Whether and how to divide the computational work. See notes for details.
|
|
19
20
|
CPUlimit (None): This is only relevant if there are `computationDivisions`: whether and how to limit the CPU usage. See notes for details.
|
|
20
|
-
**keywordArguments: Datatype management.
|
|
21
|
+
**keywordArguments: Datatype management. See `outfitCountFolds` for details.
|
|
21
22
|
Returns:
|
|
22
23
|
foldsTotal: Total number of distinct ways to fold a map of the given dimensions.
|
|
23
24
|
|
|
@@ -40,16 +41,13 @@ def countFolds(listDimensions: Sequence[int]
|
|
|
40
41
|
"""
|
|
41
42
|
stateUniversal: computationState = outfitCountFolds(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit, **keywordArguments)
|
|
42
43
|
|
|
43
|
-
pathFilenameFoldsTotal = None
|
|
44
|
-
if pathLikeWriteFoldsTotal is not None:
|
|
45
|
-
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateUniversal['mapShape'], pathLikeWriteFoldsTotal)
|
|
46
|
-
|
|
47
44
|
dispatcher = getDispatcherCallable()
|
|
48
45
|
dispatcher(**stateUniversal)
|
|
49
46
|
|
|
50
47
|
foldsTotal = int(stateUniversal['foldGroups'][0:-1].sum() * stateUniversal['foldGroups'][-1])
|
|
51
48
|
|
|
52
|
-
if
|
|
49
|
+
if pathLikeWriteFoldsTotal is not None:
|
|
50
|
+
pathFilenameFoldsTotal: Path = getPathFilenameFoldsTotal(stateUniversal['mapShape'], pathLikeWriteFoldsTotal)
|
|
53
51
|
saveFoldsTotal(pathFilenameFoldsTotal, foldsTotal)
|
|
54
52
|
|
|
55
53
|
return foldsTotal
|
mapFolding/beDRY.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""A relatively stable API for oft-needed functionality."""
|
|
2
2
|
from mapFolding import (
|
|
3
3
|
computationState,
|
|
4
|
+
getDatatypeModule,
|
|
4
5
|
getPathJobRootDEFAULT,
|
|
5
6
|
hackSSOTdatatype,
|
|
6
7
|
hackSSOTdtype,
|
|
@@ -9,35 +10,39 @@ from mapFolding import (
|
|
|
9
10
|
setDatatypeElephino,
|
|
10
11
|
setDatatypeFoldsTotal,
|
|
11
12
|
setDatatypeLeavesTotal,
|
|
13
|
+
setDatatypeModule,
|
|
12
14
|
)
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
from numba import get_num_threads, set_num_threads
|
|
13
17
|
from numpy import dtype, integer, ndarray
|
|
14
18
|
from numpy.typing import DTypeLike, NDArray
|
|
15
|
-
from
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from sys import maxsize as sysMaxsize
|
|
21
|
+
from typing import Any
|
|
16
22
|
from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
17
|
-
import numba
|
|
18
23
|
import numpy
|
|
19
24
|
import os
|
|
20
|
-
import pathlib
|
|
21
|
-
import sys
|
|
22
25
|
|
|
23
|
-
def getFilenameFoldsTotal(mapShape:
|
|
24
|
-
"""
|
|
26
|
+
def getFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]]) -> str:
|
|
27
|
+
"""Imagine your computer has been counting folds for 70 hours, and when it tries to save your newly discovered value,
|
|
28
|
+
the filename is invalid. I bet you think this function is more important after that thought experiment.
|
|
29
|
+
|
|
30
|
+
Make a standardized filename for the computed value `foldsTotal`.
|
|
25
31
|
|
|
26
32
|
The filename takes into account
|
|
27
33
|
- the dimensions of the map, aka `mapShape`, aka `listDimensions`
|
|
28
34
|
- no spaces in the filename
|
|
29
|
-
- safe filesystem characters
|
|
35
|
+
- safe filesystem characters
|
|
30
36
|
- unique extension
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- if 'p' is still the first character, I picked that because it was the original identifier for the map shape in Lunnan's code
|
|
37
|
+
- Python-safe strings:
|
|
38
|
+
- no starting with a number
|
|
39
|
+
- no reserved words
|
|
40
|
+
- no dashes or other special characters
|
|
41
|
+
- uh, I can't remember, but I found some other frustrating limitations
|
|
42
|
+
- if 'p' is still the first character of the filename, I picked that because it was the original identifier for the map shape in Lunnan's code
|
|
38
43
|
|
|
39
44
|
Parameters:
|
|
40
|
-
mapShape: A sequence of integers representing the dimensions of the map
|
|
45
|
+
mapShape: A sequence of integers representing the dimensions of the map.
|
|
41
46
|
|
|
42
47
|
Returns:
|
|
43
48
|
filenameFoldsTotal: A filename string in format 'pNxM.foldsTotal' where N,M are sorted dimensions
|
|
@@ -62,13 +67,13 @@ def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
|
62
67
|
else:
|
|
63
68
|
productDimensions = 1
|
|
64
69
|
for dimension in listPositive:
|
|
65
|
-
if dimension >
|
|
70
|
+
if dimension > sysMaxsize // productDimensions:
|
|
66
71
|
raise OverflowError(f"I received {dimension=} in {listDimensions=}, but the product of the dimensions exceeds the maximum size of an integer on this system.")
|
|
67
72
|
productDimensions *= dimension
|
|
68
73
|
|
|
69
74
|
return productDimensions
|
|
70
75
|
|
|
71
|
-
def getPathFilenameFoldsTotal(mapShape:
|
|
76
|
+
def getPathFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]], pathLikeWriteFoldsTotal: str | os.PathLike[str] | None = None) -> Path:
|
|
72
77
|
"""Get a standardized path and filename for the computed value `foldsTotal`.
|
|
73
78
|
|
|
74
79
|
If you provide a directory, the function will append a standardized filename. If you provide a filename
|
|
@@ -82,7 +87,7 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
|
|
|
82
87
|
Returns:
|
|
83
88
|
pathFilenameFoldsTotal: Absolute path and filename.
|
|
84
89
|
"""
|
|
85
|
-
pathLikeSherpa =
|
|
90
|
+
pathLikeSherpa = Path(pathLikeWriteFoldsTotal) if pathLikeWriteFoldsTotal is not None else None
|
|
86
91
|
if not pathLikeSherpa:
|
|
87
92
|
pathLikeSherpa = getPathJobRootDEFAULT()
|
|
88
93
|
if pathLikeSherpa.is_dir():
|
|
@@ -95,35 +100,38 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
|
|
|
95
100
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
96
101
|
return pathFilenameFoldsTotal
|
|
97
102
|
|
|
98
|
-
def getTaskDivisions(computationDivisions:
|
|
103
|
+
def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, CPUlimit: bool | float | int | None, listDimensions: Sequence[int]) -> int:
|
|
99
104
|
"""
|
|
100
|
-
Determines whether
|
|
105
|
+
Determines whether to divide the computation into tasks and how many divisions.
|
|
101
106
|
|
|
102
107
|
Parameters
|
|
103
108
|
----------
|
|
104
|
-
computationDivisions (None)
|
|
109
|
+
computationDivisions (None)
|
|
105
110
|
Specifies how to divide computations:
|
|
106
|
-
- None
|
|
107
|
-
- int: direct set the number of task divisions; cannot exceed the map's total leaves
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
- `None`: no division of the computation into tasks; sets task divisions to 0.
|
|
112
|
+
- int: direct set the number of task divisions; cannot exceed the map's total leaves.
|
|
113
|
+
- `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
|
|
114
|
+
- `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
|
|
115
|
+
concurrencyLimit
|
|
116
|
+
Maximum number of concurrent tasks allowed.
|
|
117
|
+
CPUlimit
|
|
118
|
+
for error reporting.
|
|
119
|
+
listDimensions
|
|
120
|
+
for error reporting.
|
|
114
121
|
|
|
115
122
|
Returns
|
|
116
123
|
-------
|
|
117
|
-
|
|
124
|
+
taskDivisions
|
|
125
|
+
How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
|
|
118
126
|
|
|
119
127
|
Raises
|
|
120
128
|
------
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
ValueError
|
|
130
|
+
If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
|
|
123
131
|
|
|
124
132
|
Notes
|
|
125
133
|
-----
|
|
126
|
-
Task divisions should not exceed total leaves
|
|
134
|
+
Task divisions should not exceed total leaves or the folds will be over-counted.
|
|
127
135
|
"""
|
|
128
136
|
taskDivisions = 0
|
|
129
137
|
leavesTotal = getLeavesTotal(listDimensions)
|
|
@@ -133,9 +141,9 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
|
|
|
133
141
|
taskDivisions = computationDivisions
|
|
134
142
|
elif isinstance(computationDivisions, str):
|
|
135
143
|
computationDivisions = computationDivisions.lower()
|
|
136
|
-
if computationDivisions ==
|
|
144
|
+
if computationDivisions == 'maximum':
|
|
137
145
|
taskDivisions = leavesTotal
|
|
138
|
-
elif computationDivisions ==
|
|
146
|
+
elif computationDivisions == 'cpu':
|
|
139
147
|
taskDivisions = min(concurrencyLimit, leavesTotal)
|
|
140
148
|
else:
|
|
141
149
|
raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
|
|
@@ -145,7 +153,7 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
|
|
|
145
153
|
|
|
146
154
|
return taskDivisions
|
|
147
155
|
|
|
148
|
-
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments:
|
|
156
|
+
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: str | None) -> ndarray[tuple[int, int, int], dtype[integer[Any]]]:
|
|
149
157
|
"""
|
|
150
158
|
Constructs a multi-dimensional connection graph representing the connections between the leaves of a map with the given dimensions.
|
|
151
159
|
Also called a Cartesian product decomposition or dimensional product mapping.
|
|
@@ -172,7 +180,7 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
|
|
|
172
180
|
for leaf1ndex in range(1, leavesTotal + 1):
|
|
173
181
|
coordinateSystem[indexDimension, leaf1ndex] = ( ((leaf1ndex - 1) // cumulativeProduct[indexDimension]) % arrayDimensions[indexDimension] + 1 )
|
|
174
182
|
|
|
175
|
-
connectionGraph = numpy.zeros((dimensionsTotal, leavesTotal + 1, leavesTotal + 1), dtype=dtype)
|
|
183
|
+
connectionGraph: ndarray[tuple[int, int, int], numpy.dtype[integer[Any]]] = numpy.zeros((dimensionsTotal, leavesTotal + 1, leavesTotal + 1), dtype=dtype)
|
|
176
184
|
for indexDimension in range(dimensionsTotal):
|
|
177
185
|
for activeLeaf1ndex in range(1, leavesTotal + 1):
|
|
178
186
|
for connectee1ndex in range(1, activeLeaf1ndex + 1):
|
|
@@ -190,23 +198,29 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
|
|
|
190
198
|
|
|
191
199
|
return connectionGraph
|
|
192
200
|
|
|
193
|
-
def makeDataContainer(shape:
|
|
201
|
+
def makeDataContainer(shape: int | tuple[int, ...], datatype: DTypeLike | None = None) -> NDArray[integer[Any]]:
|
|
194
202
|
"""Create a zeroed-out `ndarray` with the given shape and datatype.
|
|
195
203
|
|
|
196
204
|
Parameters:
|
|
197
205
|
shape: The shape of the array. Can be an integer for 1D arrays
|
|
198
206
|
or a tuple of integers for multi-dimensional arrays.
|
|
199
|
-
datatype: The desired data type for the array.
|
|
200
|
-
If None
|
|
207
|
+
datatype ('dtypeFoldsTotal'): The desired data type for the array.
|
|
208
|
+
If `None`, defaults to 'dtypeFoldsTotal'. Defaults to None.
|
|
201
209
|
|
|
202
210
|
Returns:
|
|
203
211
|
dataContainer: A new array of given shape and type, filled with zeros.
|
|
212
|
+
|
|
213
|
+
Notes:
|
|
214
|
+
If a version of the algorithm were to use something other than numpy, such as JAX or CUDA, because other
|
|
215
|
+
functions use this function, it would be much easier to change the datatype "ecosystem".
|
|
204
216
|
"""
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
numpyDtype = datatype or hackSSOTdtype('dtypeFoldsTotal')
|
|
218
|
+
if 'numpy' == getDatatypeModule():
|
|
219
|
+
return numpy.zeros(shape, dtype=numpyDtype)
|
|
220
|
+
else:
|
|
221
|
+
raise NotImplementedError("Somebody done broke it.")
|
|
208
222
|
|
|
209
|
-
def outfitCountFolds(listDimensions: Sequence[int], computationDivisions:
|
|
223
|
+
def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: int | str | None = None, CPUlimit: bool | float | int | None = None, **keywordArguments: str | bool | None) -> computationState:
|
|
210
224
|
"""
|
|
211
225
|
Initializes and configures the computation state for map folding computations.
|
|
212
226
|
|
|
@@ -214,11 +228,12 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
|
|
|
214
228
|
listDimensions: The dimensions of the map to be folded
|
|
215
229
|
computationDivisions (None): see `getTaskDivisions`
|
|
216
230
|
CPUlimit (None): see `setCPUlimit`
|
|
217
|
-
**keywordArguments: Datatype management.
|
|
231
|
+
**keywordArguments: Datatype management, it's complicated: see the code below.
|
|
218
232
|
|
|
219
233
|
Returns:
|
|
220
234
|
stateInitialized: The initialized computation state
|
|
221
235
|
"""
|
|
236
|
+
# keywordArguments START
|
|
222
237
|
kwourGrapes = keywordArguments.get('sourGrapes', None)
|
|
223
238
|
if kwourGrapes:
|
|
224
239
|
sourGrapes = True
|
|
@@ -240,6 +255,13 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
|
|
|
240
255
|
ImaSetTheDatatype = str(ImaSetTheDatatype)
|
|
241
256
|
setDatatypeLeavesTotal(ImaSetTheDatatype, sourGrapes)
|
|
242
257
|
|
|
258
|
+
# NOTE well: this might be only hypothetical because as of this writing, `makeDataContainer` only makes numpy.zeros. But it's here in case things change.
|
|
259
|
+
ImaSetTheDatatype = keywordArguments.get('datatypeModule', None)
|
|
260
|
+
if ImaSetTheDatatype:
|
|
261
|
+
ImaSetTheDatatype = str(ImaSetTheDatatype)
|
|
262
|
+
setDatatypeModule(ImaSetTheDatatype, sourGrapes)
|
|
263
|
+
# keywordArguments END
|
|
264
|
+
|
|
243
265
|
my = makeDataContainer(len(indexMy), hackSSOTdtype('my'))
|
|
244
266
|
|
|
245
267
|
mapShape = tuple(sorted(validateListDimensions(listDimensions)))
|
|
@@ -263,18 +285,18 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
|
|
|
263
285
|
|
|
264
286
|
return stateInitialized
|
|
265
287
|
|
|
266
|
-
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') ->
|
|
288
|
+
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') -> list[int]:
|
|
267
289
|
"""
|
|
268
|
-
Parse and validate dimensions are non-negative integers.
|
|
290
|
+
Parse and validate the dimensions are non-negative integers.
|
|
269
291
|
|
|
270
292
|
Parameters:
|
|
271
|
-
dimensions: Sequence of integers representing dimensions
|
|
272
|
-
parameterName ('listDimensions'): Name of the parameter for error messages. Defaults to 'listDimensions'
|
|
293
|
+
dimensions: Sequence of integers representing dimensions.
|
|
294
|
+
parameterName ('listDimensions'): Name of the parameter for error messages. Defaults to 'listDimensions'.
|
|
273
295
|
Returns:
|
|
274
|
-
listNonNegative: List of validated non-negative integers
|
|
296
|
+
listNonNegative: List of validated non-negative integers.
|
|
275
297
|
Raises:
|
|
276
|
-
ValueError: If any dimension is negative or if the list is empty
|
|
277
|
-
TypeError: If any element cannot be converted to integer (raised by intInnit)
|
|
298
|
+
ValueError: If any dimension is negative or if the list is empty.
|
|
299
|
+
TypeError: If any element cannot be converted to integer (raised by `intInnit`).
|
|
278
300
|
"""
|
|
279
301
|
listValidated = intInnit(dimensions, parameterName)
|
|
280
302
|
listNonNegative = []
|
|
@@ -285,7 +307,7 @@ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensi
|
|
|
285
307
|
|
|
286
308
|
return listNonNegative
|
|
287
309
|
|
|
288
|
-
def saveFoldsTotal(pathFilename:
|
|
310
|
+
def saveFoldsTotal(pathFilename: str | os.PathLike[str], foldsTotal: int) -> None:
|
|
289
311
|
"""
|
|
290
312
|
Save foldsTotal with multiple fallback mechanisms.
|
|
291
313
|
|
|
@@ -294,7 +316,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
|
|
|
294
316
|
foldsTotal: Critical computed value to save
|
|
295
317
|
"""
|
|
296
318
|
try:
|
|
297
|
-
pathFilenameFoldsTotal =
|
|
319
|
+
pathFilenameFoldsTotal = Path(pathFilename)
|
|
298
320
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
299
321
|
pathFilenameFoldsTotal.write_text(str(foldsTotal))
|
|
300
322
|
except Exception as ERRORmessage:
|
|
@@ -312,7 +334,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
|
|
|
312
334
|
except Exception:
|
|
313
335
|
print(foldsTotal)
|
|
314
336
|
|
|
315
|
-
def setCPUlimit(CPUlimit:
|
|
337
|
+
def setCPUlimit(CPUlimit: Any | None) -> int:
|
|
316
338
|
"""Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
|
|
317
339
|
|
|
318
340
|
Parameters:
|
|
@@ -334,11 +356,12 @@ def setCPUlimit(CPUlimit: Optional[Any]) -> int:
|
|
|
334
356
|
CPUlimit = oopsieKwargsie(CPUlimit)
|
|
335
357
|
|
|
336
358
|
concurrencyLimit = int(defineConcurrencyLimit(CPUlimit))
|
|
337
|
-
|
|
359
|
+
set_num_threads(concurrencyLimit)
|
|
360
|
+
concurrencyLimit = get_num_threads()
|
|
338
361
|
|
|
339
362
|
return concurrencyLimit
|
|
340
363
|
|
|
341
|
-
def validateListDimensions(listDimensions: Sequence[int]) ->
|
|
364
|
+
def validateListDimensions(listDimensions: Sequence[int]) -> list[int]:
|
|
342
365
|
"""
|
|
343
366
|
Validates and sorts a sequence of at least two positive dimensions.
|
|
344
367
|
|
|
@@ -349,7 +372,7 @@ def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
|
|
|
349
372
|
dimensionsValidSorted: A list, with at least two elements, of only positive integers.
|
|
350
373
|
|
|
351
374
|
Raises:
|
|
352
|
-
ValueError: If the input listDimensions is
|
|
375
|
+
ValueError: If the input listDimensions is empty.
|
|
353
376
|
NotImplementedError: If the resulting list of positive dimensions has fewer than two elements.
|
|
354
377
|
"""
|
|
355
378
|
if not listDimensions:
|
mapFolding/oeis.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
"""Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS);
|
|
2
|
-
|
|
1
|
+
"""Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS); _only_ things that implement _only_ the OEIS."""
|
|
2
|
+
from collections.abc import Callable
|
|
3
3
|
from datetime import datetime, timedelta
|
|
4
4
|
from mapFolding import countFolds, getPathPackage
|
|
5
|
-
from typing import Any,
|
|
5
|
+
from typing import Any, Final, TYPE_CHECKING
|
|
6
6
|
import argparse
|
|
7
7
|
import pathlib
|
|
8
8
|
import random
|
|
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
|
|
|
17
17
|
else:
|
|
18
18
|
TypedDict = dict
|
|
19
19
|
|
|
20
|
+
cacheDays = 7
|
|
21
|
+
|
|
20
22
|
"""
|
|
21
23
|
Section: make `settingsOEIS`"""
|
|
22
24
|
|
|
@@ -24,15 +26,15 @@ _pathCache = getPathPackage() / ".cache"
|
|
|
24
26
|
|
|
25
27
|
class SettingsOEIS(TypedDict):
|
|
26
28
|
description: str
|
|
29
|
+
getMapShape: Callable[[int], list[int]]
|
|
27
30
|
offset: int
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
valuesTestValidation: List[int]
|
|
31
|
+
valuesBenchmark: list[int]
|
|
32
|
+
valuesKnown: dict[int, int]
|
|
33
|
+
valuesTestParallelization: list[int]
|
|
34
|
+
valuesTestValidation: list[int]
|
|
33
35
|
valueUnknown: int
|
|
34
36
|
|
|
35
|
-
settingsOEIShardcodedValues:
|
|
37
|
+
settingsOEIShardcodedValues: dict[str, dict[str, Any]] = {
|
|
36
38
|
'A001415': {
|
|
37
39
|
'getMapShape': lambda n: sorted([2, n]),
|
|
38
40
|
'valuesBenchmark': [14],
|
|
@@ -65,10 +67,10 @@ settingsOEIShardcodedValues: Dict[str, Dict[str, Any]] = {
|
|
|
65
67
|
},
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
oeisIDsImplemented: Final[
|
|
70
|
+
oeisIDsImplemented: Final[list[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
|
|
69
71
|
"""Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
|
|
70
72
|
|
|
71
|
-
def
|
|
73
|
+
def validateOEISid(oeisIDcandidate: str) -> str:
|
|
72
74
|
"""
|
|
73
75
|
Validates an OEIS sequence ID against implemented sequences.
|
|
74
76
|
|
|
@@ -98,13 +100,14 @@ def _validateOEISid(oeisIDcandidate: str) -> str:
|
|
|
98
100
|
f"Available sequences:\n{_formatOEISsequenceInfo()}"
|
|
99
101
|
)
|
|
100
102
|
|
|
101
|
-
def
|
|
102
|
-
oeisID =
|
|
103
|
+
def getFilenameOEISbFile(oeisID: str) -> str:
|
|
104
|
+
oeisID = validateOEISid(oeisID)
|
|
103
105
|
return f"b{oeisID[1:]}.txt"
|
|
104
106
|
|
|
105
|
-
def _parseBFileOEIS(OEISbFile: str, oeisID: str) ->
|
|
107
|
+
def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
|
|
106
108
|
"""
|
|
107
109
|
Parses the content of an OEIS b-file for a given sequence ID.
|
|
110
|
+
|
|
108
111
|
This function processes a multiline string representing an OEIS b-file and
|
|
109
112
|
creates a dictionary mapping integer indices to their corresponding sequence
|
|
110
113
|
values. The first line of the b-file is expected to contain a comment that
|
|
@@ -133,8 +136,7 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
|
|
|
133
136
|
OEISsequence[n] = aOFn
|
|
134
137
|
return OEISsequence
|
|
135
138
|
|
|
136
|
-
def
|
|
137
|
-
cacheDays = 7
|
|
139
|
+
def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
138
140
|
tryCache = False
|
|
139
141
|
if pathFilenameCache.exists():
|
|
140
142
|
fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
|
|
@@ -144,7 +146,7 @@ def _getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
|
144
146
|
if tryCache:
|
|
145
147
|
try:
|
|
146
148
|
oeisInformation = pathFilenameCache.read_text()
|
|
147
|
-
except
|
|
149
|
+
except OSError:
|
|
148
150
|
tryCache = False
|
|
149
151
|
|
|
150
152
|
if not tryCache:
|
|
@@ -158,7 +160,7 @@ def _getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
|
158
160
|
|
|
159
161
|
return oeisInformation
|
|
160
162
|
|
|
161
|
-
def
|
|
163
|
+
def getOEISidValues(oeisID: str) -> dict[int, int]:
|
|
162
164
|
"""
|
|
163
165
|
Retrieves the specified OEIS sequence as a dictionary mapping integer indices
|
|
164
166
|
to their corresponding values.
|
|
@@ -177,21 +179,21 @@ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
|
177
179
|
IOError: If there is an error reading from or writing to the local cache.
|
|
178
180
|
"""
|
|
179
181
|
|
|
180
|
-
pathFilenameCache = _pathCache /
|
|
181
|
-
url = f"https://oeis.org/{oeisID}/{
|
|
182
|
+
pathFilenameCache = _pathCache / getFilenameOEISbFile(oeisID)
|
|
183
|
+
url = f"https://oeis.org/{oeisID}/{getFilenameOEISbFile(oeisID)}"
|
|
182
184
|
|
|
183
|
-
oeisInformation =
|
|
185
|
+
oeisInformation = getOEISofficial(pathFilenameCache, url)
|
|
184
186
|
|
|
185
187
|
if oeisInformation:
|
|
186
188
|
return _parseBFileOEIS(oeisInformation, oeisID)
|
|
187
189
|
return {-1: -1}
|
|
188
190
|
|
|
189
|
-
def
|
|
190
|
-
oeisID =
|
|
191
|
+
def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
192
|
+
oeisID = validateOEISid(oeisID)
|
|
191
193
|
pathFilenameCache = _pathCache / f"{oeisID}.txt"
|
|
192
194
|
url = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
|
|
193
195
|
|
|
194
|
-
oeisInformation =
|
|
196
|
+
oeisInformation = getOEISofficial(pathFilenameCache, url)
|
|
195
197
|
|
|
196
198
|
if not oeisInformation:
|
|
197
199
|
return "Not found", -1
|
|
@@ -218,11 +220,11 @@ def _getOEISidInformation(oeisID: str) -> Tuple[str, int]:
|
|
|
218
220
|
description = ' '.join(description_parts)
|
|
219
221
|
return description, offset
|
|
220
222
|
|
|
221
|
-
def makeSettingsOEIS() ->
|
|
223
|
+
def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
|
|
222
224
|
settingsTarget = {}
|
|
223
225
|
for oeisID in oeisIDsImplemented:
|
|
224
|
-
valuesKnownSherpa =
|
|
225
|
-
descriptionSherpa, offsetSherpa =
|
|
226
|
+
valuesKnownSherpa = getOEISidValues(oeisID)
|
|
227
|
+
descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
|
|
226
228
|
settingsTarget[oeisID] = SettingsOEIS(
|
|
227
229
|
description=descriptionSherpa,
|
|
228
230
|
offset=offsetSherpa,
|
|
@@ -235,7 +237,7 @@ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
|
|
|
235
237
|
)
|
|
236
238
|
return settingsTarget
|
|
237
239
|
|
|
238
|
-
settingsOEIS:
|
|
240
|
+
settingsOEIS: dict[str, SettingsOEIS] = makeSettingsOEIS()
|
|
239
241
|
"""All values and settings for `oeisIDsImplemented`."""
|
|
240
242
|
|
|
241
243
|
"""
|
|
@@ -282,12 +284,12 @@ def oeisIDfor_n(oeisID: str, n: int) -> int:
|
|
|
282
284
|
ValueError: If n is negative.
|
|
283
285
|
KeyError: If the OEIS sequence ID is not directly implemented.
|
|
284
286
|
"""
|
|
285
|
-
oeisID =
|
|
287
|
+
oeisID = validateOEISid(oeisID)
|
|
286
288
|
|
|
287
289
|
if not isinstance(n, int) or n < 0:
|
|
288
290
|
raise ValueError("`n` must be non-negative integer.")
|
|
289
291
|
|
|
290
|
-
listDimensions = settingsOEIS[oeisID]['getMapShape'](n)
|
|
292
|
+
listDimensions: list[int] = settingsOEIS[oeisID]['getMapShape'](n)
|
|
291
293
|
|
|
292
294
|
if n <= 1 or len(listDimensions) < 2:
|
|
293
295
|
offset = settingsOEIS[oeisID]['offset']
|
|
@@ -308,9 +310,9 @@ def OEIS_for_n() -> None:
|
|
|
308
310
|
parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
|
|
309
311
|
parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
|
|
310
312
|
|
|
311
|
-
argumentsCLI = parserCLI.parse_args()
|
|
313
|
+
argumentsCLI: argparse.Namespace = parserCLI.parse_args()
|
|
312
314
|
|
|
313
|
-
timeStart = time.perf_counter()
|
|
315
|
+
timeStart: float = time.perf_counter()
|
|
314
316
|
|
|
315
317
|
try:
|
|
316
318
|
print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
|
|
@@ -328,7 +330,7 @@ def clearOEIScache() -> None:
|
|
|
328
330
|
return
|
|
329
331
|
for oeisID in settingsOEIS:
|
|
330
332
|
( _pathCache / f"{oeisID}.txt" ).unlink(missing_ok=True)
|
|
331
|
-
( _pathCache /
|
|
333
|
+
( _pathCache / getFilenameOEISbFile(oeisID) ).unlink(missing_ok=True)
|
|
332
334
|
print(f"Cache cleared from {_pathCache}")
|
|
333
335
|
|
|
334
336
|
def getOEISids() -> None:
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
1
2
|
from mapFolding import getPathFilenameFoldsTotal, computationState, outfitCountFolds, getAlgorithmSource
|
|
3
|
+
from pathlib import Path
|
|
2
4
|
from types import ModuleType
|
|
3
|
-
from typing import Any, Literal,
|
|
4
|
-
import pathlib
|
|
5
|
+
from typing import Any, Literal, overload
|
|
5
6
|
import pickle
|
|
6
7
|
|
|
7
8
|
@overload
|
|
8
|
-
def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[True] , **keywordArguments:
|
|
9
|
+
def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[True] , **keywordArguments: str | None) -> Path: ...
|
|
9
10
|
@overload
|
|
10
|
-
def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[False] , **keywordArguments:
|
|
11
|
-
def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keywordArguments:
|
|
11
|
+
def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[False] , **keywordArguments: str | None) -> computationState: ...
|
|
12
|
+
def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keywordArguments: Any | None) -> computationState | Path:
|
|
12
13
|
"""
|
|
13
14
|
Creates a computation state job for map folding calculations and optionally saves it to disk.
|
|
14
15
|
|
|
@@ -27,7 +28,7 @@ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keyw
|
|
|
27
28
|
|
|
28
29
|
Returns
|
|
29
30
|
-------
|
|
30
|
-
Union[computationState,
|
|
31
|
+
Union[computationState, Path]
|
|
31
32
|
If writeJob is False, returns the computation state object.
|
|
32
33
|
If writeJob is True, returns the Path object pointing to the saved state file.
|
|
33
34
|
|
|
@@ -47,7 +48,7 @@ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keyw
|
|
|
47
48
|
|
|
48
49
|
pathFilenameChopChop = getPathFilenameFoldsTotal(stateUniversal['mapShape'])
|
|
49
50
|
suffix = pathFilenameChopChop.suffix
|
|
50
|
-
pathJob =
|
|
51
|
+
pathJob = Path(str(pathFilenameChopChop)[0:-len(suffix)])
|
|
51
52
|
pathJob.mkdir(parents=True, exist_ok=True)
|
|
52
53
|
pathFilenameJob = pathJob / 'stateJob.pkl'
|
|
53
54
|
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
from mapFolding import getAlgorithmSource, getPathSyntheticModules
|
|
2
2
|
from mapFolding import setDatatypeModule, setDatatypeFoldsTotal, setDatatypeElephino, setDatatypeLeavesTotal
|
|
3
|
-
from typing import Optional
|
|
4
3
|
import ast
|
|
5
4
|
import inspect
|
|
6
5
|
import pathlib
|
|
7
|
-
import sys
|
|
8
6
|
|
|
9
7
|
def transformPythonToJAX(codePython: str) -> None:
|
|
10
8
|
astPython = ast.parse(codePython)
|
|
11
9
|
|
|
12
|
-
def writeJax(*, codeSource:
|
|
10
|
+
def writeJax(*, codeSource: str | None = None, pathFilenameAlgorithm: pathlib.Path | None = None, pathFilenameDestination: pathlib.Path | None = None) -> None:
|
|
13
11
|
if codeSource is None and pathFilenameAlgorithm is None:
|
|
14
12
|
algorithmSource = getAlgorithmSource()
|
|
15
13
|
codeSource = inspect.getsource(algorithmSource)
|