mapFolding 0.4.3__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapFolding/__init__.py +93 -58
- mapFolding/basecamp.py +9 -11
- mapFolding/beDRY.py +24 -23
- mapFolding/oeis.py +47 -45
- mapFolding/theDao.py +48 -48
- mapFolding/theSSOT.py +22 -20
- mapFolding/theSSOTdatatypes.py +20 -32
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.1.dist-info}/METADATA +3 -1
- mapFolding-0.5.1.dist-info/RECORD +14 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.1.dist-info}/top_level.txt +0 -1
- mapFolding/reference/flattened.py +0 -377
- mapFolding/reference/hunterNumba.py +0 -132
- mapFolding/reference/irvineJavaPort.py +0 -120
- mapFolding/reference/jax.py +0 -208
- mapFolding/reference/lunnan.py +0 -153
- mapFolding/reference/lunnanNumpy.py +0 -123
- mapFolding/reference/lunnanWhile.py +0 -121
- mapFolding/reference/rotatedEntryPoint.py +0 -240
- mapFolding/reference/total_countPlus1vsPlusN.py +0 -211
- mapFolding/someAssemblyRequired/__init__.py +0 -5
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +0 -19
- mapFolding/someAssemblyRequired/makeJob.py +0 -55
- mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +0 -29
- mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -340
- mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +0 -396
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +0 -162
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -129
- mapFolding/syntheticModules/numbaCount.py +0 -158
- mapFolding/syntheticModules/numba_doTheNeedful.py +0 -13
- mapFolding-0.4.3.dist-info/RECORD +0 -40
- tests/__init__.py +0 -1
- tests/conftest.py +0 -306
- tests/test_computations.py +0 -43
- tests/test_oeis.py +0 -129
- tests/test_other.py +0 -171
- tests/test_tasks.py +0 -40
- tests/test_types.py +0 -5
- /mapFolding/{syntheticModules/__init__.py → py.typed} +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.1.dist-info}/LICENSE +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.1.dist-info}/WHEEL +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.1.dist-info}/entry_points.txt +0 -0
mapFolding/__init__.py
CHANGED
|
@@ -1,69 +1,104 @@
|
|
|
1
|
+
from collections import defaultdict
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
_dictionaryListsImportFrom: dict[str, list[str]] = defaultdict(list)
|
|
6
|
+
|
|
7
|
+
def __getattr__(name: str):
|
|
8
|
+
if name not in _mapSymbolToModule:
|
|
9
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
moduleAsStr: str = _mapSymbolToModule[name]
|
|
13
|
+
module: ModuleType = importlib.import_module(moduleAsStr)
|
|
14
|
+
blankSymbol = getattr(module, name)
|
|
15
|
+
except (ImportError, ModuleNotFoundError, AttributeError):
|
|
16
|
+
raise
|
|
17
|
+
|
|
18
|
+
# The need to inject into globals tells us that the symbol has not actually been imported
|
|
19
|
+
globals()[name] = blankSymbol
|
|
20
|
+
return blankSymbol
|
|
21
|
+
|
|
22
|
+
_dictionaryListsImportFrom['mapFolding.basecamp'].extend([
|
|
23
|
+
'countFolds',
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
_dictionaryListsImportFrom['mapFolding.beDRY'].extend([
|
|
27
|
+
'getFilenameFoldsTotal',
|
|
28
|
+
'getPathFilenameFoldsTotal',
|
|
29
|
+
'outfitCountFolds',
|
|
30
|
+
'saveFoldsTotal',
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
_dictionaryListsImportFrom['mapFolding.oeis'].extend([
|
|
34
|
+
'clearOEIScache',
|
|
35
|
+
'getOEISids',
|
|
36
|
+
'oeisIDfor_n',
|
|
37
|
+
])
|
|
38
|
+
|
|
1
39
|
# fundamentals
|
|
2
|
-
|
|
3
|
-
computationState,
|
|
4
|
-
EnumIndices,
|
|
5
|
-
getDispatcherCallable,
|
|
6
|
-
getPathPackage,
|
|
7
|
-
indexMy,
|
|
8
|
-
indexTrack,
|
|
9
|
-
myPackageNameIs,
|
|
10
|
-
)
|
|
40
|
+
_dictionaryListsImportFrom['mapFolding.theSSOT'].extend([
|
|
41
|
+
'computationState',
|
|
42
|
+
'EnumIndices',
|
|
43
|
+
'getDispatcherCallable',
|
|
44
|
+
'getPathPackage',
|
|
45
|
+
'indexMy',
|
|
46
|
+
'indexTrack',
|
|
47
|
+
'myPackageNameIs',
|
|
48
|
+
])
|
|
11
49
|
|
|
12
50
|
# Datatype management
|
|
13
|
-
|
|
14
|
-
getDatatypeModule,
|
|
15
|
-
hackSSOTdatatype,
|
|
16
|
-
hackSSOTdtype,
|
|
17
|
-
setDatatypeElephino,
|
|
18
|
-
setDatatypeFoldsTotal,
|
|
19
|
-
setDatatypeLeavesTotal,
|
|
20
|
-
setDatatypeModule,
|
|
21
|
-
)
|
|
51
|
+
_dictionaryListsImportFrom['mapFolding.theSSOT'].extend([
|
|
52
|
+
'getDatatypeModule',
|
|
53
|
+
'hackSSOTdatatype',
|
|
54
|
+
'hackSSOTdtype',
|
|
55
|
+
'setDatatypeElephino',
|
|
56
|
+
'setDatatypeFoldsTotal',
|
|
57
|
+
'setDatatypeLeavesTotal',
|
|
58
|
+
'setDatatypeModule',
|
|
59
|
+
])
|
|
22
60
|
|
|
23
61
|
# Synthesize modules
|
|
24
|
-
|
|
25
|
-
formatFilenameModuleDEFAULT,
|
|
26
|
-
getAlgorithmDispatcher,
|
|
27
|
-
getAlgorithmSource,
|
|
28
|
-
getPathJobRootDEFAULT,
|
|
29
|
-
getPathSyntheticModules,
|
|
30
|
-
moduleOfSyntheticModules,
|
|
31
|
-
Z0Z_getDatatypeModuleScalar,
|
|
32
|
-
Z0Z_getDecoratorCallable,
|
|
33
|
-
Z0Z_setDatatypeModuleScalar,
|
|
34
|
-
Z0Z_setDecoratorCallable,
|
|
35
|
-
Z0Z_identifierCountFolds,
|
|
36
|
-
)
|
|
62
|
+
_dictionaryListsImportFrom['mapFolding.theSSOT'].extend([
|
|
63
|
+
'formatFilenameModuleDEFAULT',
|
|
64
|
+
'getAlgorithmDispatcher',
|
|
65
|
+
'getAlgorithmSource',
|
|
66
|
+
'getPathJobRootDEFAULT',
|
|
67
|
+
'getPathSyntheticModules',
|
|
68
|
+
'moduleOfSyntheticModules',
|
|
69
|
+
'Z0Z_getDatatypeModuleScalar',
|
|
70
|
+
'Z0Z_getDecoratorCallable',
|
|
71
|
+
'Z0Z_setDatatypeModuleScalar',
|
|
72
|
+
'Z0Z_setDecoratorCallable',
|
|
73
|
+
'Z0Z_identifierCountFolds',
|
|
74
|
+
])
|
|
37
75
|
|
|
38
76
|
# Parameters for the prima donna
|
|
39
|
-
|
|
40
|
-
ParametersNumba,
|
|
41
|
-
parametersNumbaDEFAULT,
|
|
42
|
-
parametersNumbaFailEarly,
|
|
43
|
-
parametersNumbaMinimum,
|
|
44
|
-
parametersNumbaParallelDEFAULT,
|
|
45
|
-
parametersNumbaSuperJit,
|
|
46
|
-
parametersNumbaSuperJitParallel,
|
|
47
|
-
)
|
|
77
|
+
_dictionaryListsImportFrom['mapFolding.theSSOT'].extend([
|
|
78
|
+
'ParametersNumba',
|
|
79
|
+
'parametersNumbaDEFAULT',
|
|
80
|
+
'parametersNumbaFailEarly',
|
|
81
|
+
'parametersNumbaMinimum',
|
|
82
|
+
'parametersNumbaParallelDEFAULT',
|
|
83
|
+
'parametersNumbaSuperJit',
|
|
84
|
+
'parametersNumbaSuperJitParallel',
|
|
85
|
+
])
|
|
48
86
|
|
|
49
87
|
# Coping
|
|
50
|
-
|
|
51
|
-
FREAKOUT,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
from mapFolding.beDRY import (
|
|
55
|
-
getFilenameFoldsTotal,
|
|
56
|
-
getPathFilenameFoldsTotal,
|
|
57
|
-
outfitCountFolds,
|
|
58
|
-
saveFoldsTotal,
|
|
59
|
-
)
|
|
88
|
+
_dictionaryListsImportFrom['mapFolding.theSSOT'].extend([
|
|
89
|
+
'FREAKOUT',
|
|
90
|
+
])
|
|
60
91
|
|
|
61
|
-
|
|
62
|
-
|
|
92
|
+
_mapSymbolToModule: dict[str, str] = {}
|
|
93
|
+
for moduleAsStr, listSymbolsAsStr in _dictionaryListsImportFrom.items():
|
|
94
|
+
for symbolAsStr in listSymbolsAsStr:
|
|
95
|
+
_mapSymbolToModule[symbolAsStr] = moduleAsStr
|
|
63
96
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
from typing import TYPE_CHECKING
|
|
98
|
+
if TYPE_CHECKING:
|
|
99
|
+
from basecamp import *
|
|
100
|
+
from beDRY import *
|
|
101
|
+
from oeis import *
|
|
102
|
+
from theDao import *
|
|
103
|
+
from theSSOT import *
|
|
104
|
+
from theSSOTdatatypes import *
|
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
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
def countFolds(listDimensions: Sequence[int]
|
|
6
|
-
, pathLikeWriteFoldsTotal:
|
|
7
|
-
, computationDivisions:
|
|
8
|
-
, CPUlimit:
|
|
9
|
-
, **keywordArguments:
|
|
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
|
|
|
@@ -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
|
@@ -12,17 +12,18 @@ from mapFolding import (
|
|
|
12
12
|
setDatatypeLeavesTotal,
|
|
13
13
|
setDatatypeModule,
|
|
14
14
|
)
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
from numba import get_num_threads, set_num_threads # type: ignore
|
|
15
17
|
from numpy import dtype, integer, ndarray
|
|
16
18
|
from numpy.typing import DTypeLike, NDArray
|
|
17
|
-
from
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from sys import maxsize as sysMaxsize
|
|
21
|
+
from typing import Any
|
|
18
22
|
from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
19
|
-
import numba
|
|
20
23
|
import numpy
|
|
21
24
|
import os
|
|
22
|
-
import pathlib
|
|
23
|
-
import sys
|
|
24
25
|
|
|
25
|
-
def getFilenameFoldsTotal(mapShape:
|
|
26
|
+
def getFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]]) -> str:
|
|
26
27
|
"""Imagine your computer has been counting folds for 70 hours, and when it tries to save your newly discovered value,
|
|
27
28
|
the filename is invalid. I bet you think this function is more important after that thought experiment.
|
|
28
29
|
|
|
@@ -66,13 +67,13 @@ def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
|
66
67
|
else:
|
|
67
68
|
productDimensions = 1
|
|
68
69
|
for dimension in listPositive:
|
|
69
|
-
if dimension >
|
|
70
|
+
if dimension > sysMaxsize // productDimensions:
|
|
70
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.")
|
|
71
72
|
productDimensions *= dimension
|
|
72
73
|
|
|
73
74
|
return productDimensions
|
|
74
75
|
|
|
75
|
-
def getPathFilenameFoldsTotal(mapShape:
|
|
76
|
+
def getPathFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]], pathLikeWriteFoldsTotal: str | os.PathLike[str] | None = None) -> Path:
|
|
76
77
|
"""Get a standardized path and filename for the computed value `foldsTotal`.
|
|
77
78
|
|
|
78
79
|
If you provide a directory, the function will append a standardized filename. If you provide a filename
|
|
@@ -86,7 +87,7 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
|
|
|
86
87
|
Returns:
|
|
87
88
|
pathFilenameFoldsTotal: Absolute path and filename.
|
|
88
89
|
"""
|
|
89
|
-
pathLikeSherpa =
|
|
90
|
+
pathLikeSherpa = Path(pathLikeWriteFoldsTotal) if pathLikeWriteFoldsTotal is not None else None
|
|
90
91
|
if not pathLikeSherpa:
|
|
91
92
|
pathLikeSherpa = getPathJobRootDEFAULT()
|
|
92
93
|
if pathLikeSherpa.is_dir():
|
|
@@ -99,7 +100,7 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
|
|
|
99
100
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
100
101
|
return pathFilenameFoldsTotal
|
|
101
102
|
|
|
102
|
-
def getTaskDivisions(computationDivisions:
|
|
103
|
+
def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, CPUlimit: bool | float | int | None, listDimensions: Sequence[int]) -> int:
|
|
103
104
|
"""
|
|
104
105
|
Determines whether to divide the computation into tasks and how many divisions.
|
|
105
106
|
|
|
@@ -138,7 +139,7 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
|
|
|
138
139
|
pass
|
|
139
140
|
elif isinstance(computationDivisions, int):
|
|
140
141
|
taskDivisions = computationDivisions
|
|
141
|
-
elif isinstance(computationDivisions, str):
|
|
142
|
+
elif isinstance(computationDivisions, str): # type: ignore 'Unnecessary isinstance call; "str" is always an instance of "str", so sayeth Pylance'. Yeah, well "User is not always an instance of "correct input" so sayeth the programmer.
|
|
142
143
|
computationDivisions = computationDivisions.lower()
|
|
143
144
|
if computationDivisions == 'maximum':
|
|
144
145
|
taskDivisions = leavesTotal
|
|
@@ -152,7 +153,7 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
|
|
|
152
153
|
|
|
153
154
|
return taskDivisions
|
|
154
155
|
|
|
155
|
-
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments:
|
|
156
|
+
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: str | None) -> ndarray[tuple[int, int, int], dtype[integer[Any]]]:
|
|
156
157
|
"""
|
|
157
158
|
Constructs a multi-dimensional connection graph representing the connections between the leaves of a map with the given dimensions.
|
|
158
159
|
Also called a Cartesian product decomposition or dimensional product mapping.
|
|
@@ -179,7 +180,7 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
|
|
|
179
180
|
for leaf1ndex in range(1, leavesTotal + 1):
|
|
180
181
|
coordinateSystem[indexDimension, leaf1ndex] = ( ((leaf1ndex - 1) // cumulativeProduct[indexDimension]) % arrayDimensions[indexDimension] + 1 )
|
|
181
182
|
|
|
182
|
-
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)
|
|
183
184
|
for indexDimension in range(dimensionsTotal):
|
|
184
185
|
for activeLeaf1ndex in range(1, leavesTotal + 1):
|
|
185
186
|
for connectee1ndex in range(1, activeLeaf1ndex + 1):
|
|
@@ -197,7 +198,7 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
|
|
|
197
198
|
|
|
198
199
|
return connectionGraph
|
|
199
200
|
|
|
200
|
-
def makeDataContainer(shape:
|
|
201
|
+
def makeDataContainer(shape: int | tuple[int, ...], datatype: DTypeLike | None = None) -> NDArray[integer[Any]]:
|
|
201
202
|
"""Create a zeroed-out `ndarray` with the given shape and datatype.
|
|
202
203
|
|
|
203
204
|
Parameters:
|
|
@@ -219,7 +220,7 @@ def makeDataContainer(shape: Union[int, Tuple[int, ...]], datatype: Optional[DTy
|
|
|
219
220
|
else:
|
|
220
221
|
raise NotImplementedError("Somebody done broke it.")
|
|
221
222
|
|
|
222
|
-
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:
|
|
223
224
|
"""
|
|
224
225
|
Initializes and configures the computation state for map folding computations.
|
|
225
226
|
|
|
@@ -284,7 +285,7 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
|
|
|
284
285
|
|
|
285
286
|
return stateInitialized
|
|
286
287
|
|
|
287
|
-
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') ->
|
|
288
|
+
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') -> list[int]:
|
|
288
289
|
"""
|
|
289
290
|
Parse and validate the dimensions are non-negative integers.
|
|
290
291
|
|
|
@@ -297,8 +298,8 @@ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensi
|
|
|
297
298
|
ValueError: If any dimension is negative or if the list is empty.
|
|
298
299
|
TypeError: If any element cannot be converted to integer (raised by `intInnit`).
|
|
299
300
|
"""
|
|
300
|
-
listValidated = intInnit(dimensions, parameterName)
|
|
301
|
-
listNonNegative = []
|
|
301
|
+
listValidated: list[int] = intInnit(dimensions, parameterName)
|
|
302
|
+
listNonNegative: list[int] = []
|
|
302
303
|
for dimension in listValidated:
|
|
303
304
|
if dimension < 0:
|
|
304
305
|
raise ValueError(f"Dimension {dimension} must be non-negative")
|
|
@@ -306,7 +307,7 @@ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensi
|
|
|
306
307
|
|
|
307
308
|
return listNonNegative
|
|
308
309
|
|
|
309
|
-
def saveFoldsTotal(pathFilename:
|
|
310
|
+
def saveFoldsTotal(pathFilename: str | os.PathLike[str], foldsTotal: int) -> None:
|
|
310
311
|
"""
|
|
311
312
|
Save foldsTotal with multiple fallback mechanisms.
|
|
312
313
|
|
|
@@ -315,7 +316,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
|
|
|
315
316
|
foldsTotal: Critical computed value to save
|
|
316
317
|
"""
|
|
317
318
|
try:
|
|
318
|
-
pathFilenameFoldsTotal =
|
|
319
|
+
pathFilenameFoldsTotal = Path(pathFilename)
|
|
319
320
|
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
320
321
|
pathFilenameFoldsTotal.write_text(str(foldsTotal))
|
|
321
322
|
except Exception as ERRORmessage:
|
|
@@ -333,7 +334,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
|
|
|
333
334
|
except Exception:
|
|
334
335
|
print(foldsTotal)
|
|
335
336
|
|
|
336
|
-
def setCPUlimit(CPUlimit:
|
|
337
|
+
def setCPUlimit(CPUlimit: Any | None) -> int:
|
|
337
338
|
"""Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
|
|
338
339
|
|
|
339
340
|
Parameters:
|
|
@@ -355,12 +356,12 @@ def setCPUlimit(CPUlimit: Optional[Any]) -> int:
|
|
|
355
356
|
CPUlimit = oopsieKwargsie(CPUlimit)
|
|
356
357
|
|
|
357
358
|
concurrencyLimit = int(defineConcurrencyLimit(CPUlimit))
|
|
358
|
-
|
|
359
|
-
concurrencyLimit =
|
|
359
|
+
set_num_threads(concurrencyLimit)
|
|
360
|
+
concurrencyLimit: int = get_num_threads()
|
|
360
361
|
|
|
361
362
|
return concurrencyLimit
|
|
362
363
|
|
|
363
|
-
def validateListDimensions(listDimensions: Sequence[int]) ->
|
|
364
|
+
def validateListDimensions(listDimensions: Sequence[int]) -> list[int]:
|
|
364
365
|
"""
|
|
365
366
|
Validates and sorts a sequence of at least two positive dimensions.
|
|
366
367
|
|
mapFolding/oeis.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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
|
+
from pathlib import Path
|
|
8
9
|
import random
|
|
9
10
|
import sys
|
|
10
11
|
import time
|
|
@@ -17,22 +18,24 @@ if TYPE_CHECKING:
|
|
|
17
18
|
else:
|
|
18
19
|
TypedDict = dict
|
|
19
20
|
|
|
21
|
+
cacheDays = 7
|
|
22
|
+
|
|
20
23
|
"""
|
|
21
24
|
Section: make `settingsOEIS`"""
|
|
22
25
|
|
|
23
|
-
_pathCache = getPathPackage() / ".cache"
|
|
26
|
+
_pathCache: Path = getPathPackage() / ".cache"
|
|
24
27
|
|
|
25
28
|
class SettingsOEIS(TypedDict):
|
|
26
29
|
description: str
|
|
27
|
-
getMapShape: Callable[[int],
|
|
30
|
+
getMapShape: Callable[[int], list[int]]
|
|
28
31
|
offset: int
|
|
29
|
-
valuesBenchmark:
|
|
30
|
-
valuesKnown:
|
|
31
|
-
valuesTestParallelization:
|
|
32
|
-
valuesTestValidation:
|
|
32
|
+
valuesBenchmark: list[int]
|
|
33
|
+
valuesKnown: dict[int, int]
|
|
34
|
+
valuesTestParallelization: list[int]
|
|
35
|
+
valuesTestValidation: list[int]
|
|
33
36
|
valueUnknown: int
|
|
34
37
|
|
|
35
|
-
settingsOEIShardcodedValues:
|
|
38
|
+
settingsOEIShardcodedValues: dict[str, dict[str, Any]] = {
|
|
36
39
|
'A001415': {
|
|
37
40
|
'getMapShape': lambda n: sorted([2, n]),
|
|
38
41
|
'valuesBenchmark': [14],
|
|
@@ -65,7 +68,7 @@ settingsOEIShardcodedValues: Dict[str, Dict[str, Any]] = {
|
|
|
65
68
|
},
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
oeisIDsImplemented: Final[
|
|
71
|
+
oeisIDsImplemented: Final[list[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
|
|
69
72
|
"""Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
|
|
70
73
|
|
|
71
74
|
def validateOEISid(oeisIDcandidate: str) -> str:
|
|
@@ -89,7 +92,7 @@ def validateOEISid(oeisIDcandidate: str) -> str:
|
|
|
89
92
|
if oeisIDcandidate in oeisIDsImplemented:
|
|
90
93
|
return oeisIDcandidate
|
|
91
94
|
else:
|
|
92
|
-
oeisIDcleaned = str(oeisIDcandidate).upper().strip()
|
|
95
|
+
oeisIDcleaned: str = str(oeisIDcandidate).upper().strip()
|
|
93
96
|
if oeisIDcleaned in oeisIDsImplemented:
|
|
94
97
|
return oeisIDcleaned
|
|
95
98
|
else:
|
|
@@ -102,7 +105,7 @@ def getFilenameOEISbFile(oeisID: str) -> str:
|
|
|
102
105
|
oeisID = validateOEISid(oeisID)
|
|
103
106
|
return f"b{oeisID[1:]}.txt"
|
|
104
107
|
|
|
105
|
-
def _parseBFileOEIS(OEISbFile: str, oeisID: str) ->
|
|
108
|
+
def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
|
|
106
109
|
"""
|
|
107
110
|
Parses the content of an OEIS b-file for a given sequence ID.
|
|
108
111
|
|
|
@@ -121,12 +124,12 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
|
|
|
121
124
|
ValueError: If the first line of the file does not indicate the expected
|
|
122
125
|
sequence ID or if the content format is invalid.
|
|
123
126
|
"""
|
|
124
|
-
bFileLines = OEISbFile.strip().splitlines()
|
|
127
|
+
bFileLines: list[str] = OEISbFile.strip().splitlines()
|
|
125
128
|
if not bFileLines.pop(0).startswith(f"# {oeisID}"):
|
|
126
129
|
warnings.warn(f"Content does not match sequence {oeisID}")
|
|
127
130
|
return {-1: -1}
|
|
128
131
|
|
|
129
|
-
OEISsequence = {}
|
|
132
|
+
OEISsequence: dict[int, int] = {}
|
|
130
133
|
for line in bFileLines:
|
|
131
134
|
if line.startswith('#'):
|
|
132
135
|
continue
|
|
@@ -135,17 +138,16 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
|
|
|
135
138
|
return OEISsequence
|
|
136
139
|
|
|
137
140
|
def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
138
|
-
cacheDays = 7
|
|
139
141
|
tryCache = False
|
|
140
142
|
if pathFilenameCache.exists():
|
|
141
|
-
fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
|
|
142
|
-
tryCache = fileAge < timedelta(days=cacheDays)
|
|
143
|
+
fileAge: timedelta = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
|
|
144
|
+
tryCache: bool = fileAge < timedelta(days=cacheDays)
|
|
143
145
|
|
|
144
|
-
oeisInformation = None
|
|
146
|
+
oeisInformation: str | None = None
|
|
145
147
|
if tryCache:
|
|
146
148
|
try:
|
|
147
149
|
oeisInformation = pathFilenameCache.read_text()
|
|
148
|
-
except
|
|
150
|
+
except OSError:
|
|
149
151
|
tryCache = False
|
|
150
152
|
|
|
151
153
|
if not tryCache:
|
|
@@ -159,7 +161,7 @@ def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
|
159
161
|
|
|
160
162
|
return oeisInformation
|
|
161
163
|
|
|
162
|
-
def getOEISidValues(oeisID: str) ->
|
|
164
|
+
def getOEISidValues(oeisID: str) -> dict[int, int]:
|
|
163
165
|
"""
|
|
164
166
|
Retrieves the specified OEIS sequence as a dictionary mapping integer indices
|
|
165
167
|
to their corresponding values.
|
|
@@ -178,26 +180,26 @@ def getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
|
178
180
|
IOError: If there is an error reading from or writing to the local cache.
|
|
179
181
|
"""
|
|
180
182
|
|
|
181
|
-
pathFilenameCache = _pathCache / getFilenameOEISbFile(oeisID)
|
|
182
|
-
url = f"https://oeis.org/{oeisID}/{getFilenameOEISbFile(oeisID)}"
|
|
183
|
+
pathFilenameCache: Path = _pathCache / getFilenameOEISbFile(oeisID)
|
|
184
|
+
url: str = f"https://oeis.org/{oeisID}/{getFilenameOEISbFile(oeisID)}"
|
|
183
185
|
|
|
184
|
-
oeisInformation = getOEISofficial(pathFilenameCache, url)
|
|
186
|
+
oeisInformation: None | str = getOEISofficial(pathFilenameCache, url)
|
|
185
187
|
|
|
186
188
|
if oeisInformation:
|
|
187
189
|
return _parseBFileOEIS(oeisInformation, oeisID)
|
|
188
190
|
return {-1: -1}
|
|
189
191
|
|
|
190
|
-
def getOEISidInformation(oeisID: str) ->
|
|
192
|
+
def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
191
193
|
oeisID = validateOEISid(oeisID)
|
|
192
|
-
pathFilenameCache = _pathCache / f"{oeisID}.txt"
|
|
193
|
-
url = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
|
|
194
|
+
pathFilenameCache: Path = _pathCache / f"{oeisID}.txt"
|
|
195
|
+
url: str = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
|
|
194
196
|
|
|
195
|
-
oeisInformation = getOEISofficial(pathFilenameCache, url)
|
|
197
|
+
oeisInformation: None | str = getOEISofficial(pathFilenameCache, url)
|
|
196
198
|
|
|
197
199
|
if not oeisInformation:
|
|
198
200
|
return "Not found", -1
|
|
199
201
|
|
|
200
|
-
description_parts = []
|
|
202
|
+
description_parts: list[str] = []
|
|
201
203
|
offset = None
|
|
202
204
|
for line in oeisInformation.splitlines():
|
|
203
205
|
if line.startswith('%N'):
|
|
@@ -206,9 +208,9 @@ def getOEISidInformation(oeisID: str) -> Tuple[str, int]:
|
|
|
206
208
|
desc_part = ' '.join(parts[2:])
|
|
207
209
|
description_parts.append(desc_part)
|
|
208
210
|
elif line.startswith('%O'):
|
|
209
|
-
parts = line.split()
|
|
211
|
+
parts: list[str] = line.split()
|
|
210
212
|
if parts[1] == oeisID:
|
|
211
|
-
offset_str = parts[2].split(',')[0]
|
|
213
|
+
offset_str: str = parts[2].split(',')[0]
|
|
212
214
|
offset = int(offset_str)
|
|
213
215
|
if not description_parts:
|
|
214
216
|
warnings.warn(f"No description found for {oeisID}")
|
|
@@ -216,13 +218,13 @@ def getOEISidInformation(oeisID: str) -> Tuple[str, int]:
|
|
|
216
218
|
if offset is None:
|
|
217
219
|
warnings.warn(f"No offset found for {oeisID}")
|
|
218
220
|
offset = -1
|
|
219
|
-
description = ' '.join(description_parts)
|
|
221
|
+
description: str = ' '.join(description_parts)
|
|
220
222
|
return description, offset
|
|
221
223
|
|
|
222
|
-
def makeSettingsOEIS() ->
|
|
223
|
-
settingsTarget = {}
|
|
224
|
+
def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
|
|
225
|
+
settingsTarget: dict[str, SettingsOEIS] = {}
|
|
224
226
|
for oeisID in oeisIDsImplemented:
|
|
225
|
-
valuesKnownSherpa = getOEISidValues(oeisID)
|
|
227
|
+
valuesKnownSherpa: dict[int, int] = getOEISidValues(oeisID)
|
|
226
228
|
descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
|
|
227
229
|
settingsTarget[oeisID] = SettingsOEIS(
|
|
228
230
|
description=descriptionSherpa,
|
|
@@ -236,7 +238,7 @@ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
|
|
|
236
238
|
)
|
|
237
239
|
return settingsTarget
|
|
238
240
|
|
|
239
|
-
settingsOEIS:
|
|
241
|
+
settingsOEIS: dict[str, SettingsOEIS] = makeSettingsOEIS()
|
|
240
242
|
"""All values and settings for `oeisIDsImplemented`."""
|
|
241
243
|
|
|
242
244
|
"""
|
|
@@ -244,8 +246,8 @@ Section: private functions"""
|
|
|
244
246
|
|
|
245
247
|
def _formatHelpText() -> str:
|
|
246
248
|
"""Format standardized help text for both CLI and interactive use."""
|
|
247
|
-
exampleOEISid = oeisIDsImplemented[0]
|
|
248
|
-
exampleN = settingsOEIS[exampleOEISid]['valuesTestValidation'][-1]
|
|
249
|
+
exampleOEISid: str = oeisIDsImplemented[0]
|
|
250
|
+
exampleN: int = settingsOEIS[exampleOEISid]['valuesTestValidation'][-1]
|
|
249
251
|
|
|
250
252
|
return (
|
|
251
253
|
"\nAvailable OEIS sequences:\n"
|
|
@@ -268,7 +270,7 @@ def _formatOEISsequenceInfo() -> str:
|
|
|
268
270
|
"""
|
|
269
271
|
Section: public functions"""
|
|
270
272
|
|
|
271
|
-
def oeisIDfor_n(oeisID: str, n: int) -> int:
|
|
273
|
+
def oeisIDfor_n(oeisID: str, n: int | Any) -> int:
|
|
272
274
|
"""
|
|
273
275
|
Calculate a(n) of a sequence from "The On-Line Encyclopedia of Integer Sequences" (OEIS).
|
|
274
276
|
|
|
@@ -288,13 +290,13 @@ def oeisIDfor_n(oeisID: str, n: int) -> int:
|
|
|
288
290
|
if not isinstance(n, int) or n < 0:
|
|
289
291
|
raise ValueError("`n` must be non-negative integer.")
|
|
290
292
|
|
|
291
|
-
listDimensions = settingsOEIS[oeisID]['getMapShape'](n)
|
|
293
|
+
listDimensions: list[int] = settingsOEIS[oeisID]['getMapShape'](n)
|
|
292
294
|
|
|
293
295
|
if n <= 1 or len(listDimensions) < 2:
|
|
294
|
-
offset = settingsOEIS[oeisID]['offset']
|
|
296
|
+
offset: int = settingsOEIS[oeisID]['offset']
|
|
295
297
|
if n < offset:
|
|
296
298
|
raise ArithmeticError(f"OEIS sequence {oeisID} is not defined at n={n}.")
|
|
297
|
-
foldsTotal = settingsOEIS[oeisID]['valuesKnown'][n]
|
|
299
|
+
foldsTotal: int = settingsOEIS[oeisID]['valuesKnown'][n]
|
|
298
300
|
return foldsTotal
|
|
299
301
|
|
|
300
302
|
return countFolds(listDimensions)
|
|
@@ -309,9 +311,9 @@ def OEIS_for_n() -> None:
|
|
|
309
311
|
parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
|
|
310
312
|
parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
|
|
311
313
|
|
|
312
|
-
argumentsCLI = parserCLI.parse_args()
|
|
314
|
+
argumentsCLI: argparse.Namespace = parserCLI.parse_args()
|
|
313
315
|
|
|
314
|
-
timeStart = time.perf_counter()
|
|
316
|
+
timeStart: float = time.perf_counter()
|
|
315
317
|
|
|
316
318
|
try:
|
|
317
319
|
print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
|
|
@@ -319,7 +321,7 @@ def OEIS_for_n() -> None:
|
|
|
319
321
|
print(f"Error: {ERRORmessage}", file=sys.stderr)
|
|
320
322
|
sys.exit(1)
|
|
321
323
|
|
|
322
|
-
timeElapsed = time.perf_counter() - timeStart
|
|
324
|
+
timeElapsed: float = time.perf_counter() - timeStart
|
|
323
325
|
print(f"Time elapsed: {timeElapsed:.3f} seconds")
|
|
324
326
|
|
|
325
327
|
def clearOEIScache() -> None:
|