mapFolding 0.5.0__py3-none-any.whl → 0.6.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 +96 -58
- mapFolding/basecamp.py +5 -7
- mapFolding/beDRY.py +11 -41
- mapFolding/oeis.py +71 -74
- mapFolding/theConfiguration.py +58 -0
- mapFolding/theDao.py +1 -1
- mapFolding/theSSOT.py +14 -48
- mapFolding/theSSOTdatatypes.py +25 -36
- mapFolding/theWrongWay.py +7 -0
- {mapFolding-0.5.0.dist-info → mapfolding-0.6.0.dist-info}/METADATA +6 -4
- mapfolding-0.6.0.dist-info/RECORD +16 -0
- {mapFolding-0.5.0.dist-info → mapfolding-0.6.0.dist-info}/WHEEL +1 -1
- {mapFolding-0.5.0.dist-info → mapfolding-0.6.0.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 -56
- mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +0 -27
- mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -345
- mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +0 -397
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +0 -155
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -123
- mapFolding/syntheticModules/numbaCount.py +0 -158
- mapFolding/syntheticModules/numba_doTheNeedful.py +0 -13
- mapFolding-0.5.0.dist-info/RECORD +0 -39
- tests/__init__.py +0 -1
- tests/conftest.py +0 -335
- tests/test_computations.py +0 -42
- tests/test_oeis.py +0 -128
- tests/test_other.py +0 -175
- tests/test_tasks.py +0 -40
- /mapFolding/{syntheticModules/__init__.py → py.typed} +0 -0
- {mapFolding-0.5.0.dist-info → mapfolding-0.6.0.dist-info}/LICENSE +0 -0
- {mapFolding-0.5.0.dist-info → mapfolding-0.6.0.dist-info}/entry_points.txt +0 -0
mapFolding/theSSOT.py
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from mapFolding.theSSOTdatatypes import
|
|
3
|
-
EnumIndices,
|
|
4
|
-
indexMy,
|
|
5
|
-
indexTrack,
|
|
6
|
-
reportDatatypeLimit,
|
|
7
|
-
setDatatypeModule,
|
|
8
|
-
setDatatypeElephino,
|
|
9
|
-
setDatatypeFoldsTotal,
|
|
10
|
-
setDatatypeLeavesTotal,
|
|
11
|
-
getDatatypeModule,
|
|
12
|
-
setInStone,
|
|
13
|
-
hackSSOTdtype,
|
|
14
|
-
hackSSOTdatatype,
|
|
15
|
-
)
|
|
2
|
+
from mapFolding.theSSOTdatatypes import *
|
|
16
3
|
from numba.core.compiler import CompilerBase as numbaCompilerBase
|
|
17
4
|
from numpy import dtype, integer, ndarray
|
|
18
5
|
from pathlib import Path
|
|
19
|
-
from sys import modules as sysModules
|
|
20
6
|
from types import ModuleType
|
|
21
7
|
from typing import Any, Final, TYPE_CHECKING, cast
|
|
22
8
|
|
|
@@ -34,49 +20,29 @@ else:
|
|
|
34
20
|
- Configuration Registry
|
|
35
21
|
- Write-Once, Read-Many (WORM) / Immutable Initialization
|
|
36
22
|
- Lazy Initialization
|
|
37
|
-
-
|
|
23
|
+
- Separate configuration from business logic
|
|
24
|
+
|
|
25
|
+
theSSOT and yourSSOT
|
|
38
26
|
|
|
39
27
|
delay realization/instantiation until a concrete value is desired
|
|
40
28
|
moment of truth: when the value is needed, not when the value is defined
|
|
41
29
|
"""
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def getPathPackage() -> Path:
|
|
51
|
-
import importlib, inspect
|
|
52
|
-
pathPackage = Path(inspect.getfile(importlib.import_module(myPackageNameIs)))
|
|
53
|
-
if pathPackage.is_file():
|
|
54
|
-
pathPackage = pathPackage.parent
|
|
55
|
-
return pathPackage
|
|
56
|
-
|
|
57
|
-
def getPathJobRootDEFAULT() -> Path:
|
|
58
|
-
if 'google.colab' in sysModules:
|
|
59
|
-
pathJobDEFAULT = Path("/content/drive/MyDrive") / "jobs"
|
|
60
|
-
else:
|
|
61
|
-
pathJobDEFAULT = getPathPackage() / "jobs"
|
|
62
|
-
return pathJobDEFAULT
|
|
31
|
+
"""
|
|
32
|
+
listDimensions: list[int]
|
|
33
|
+
mapShape
|
|
34
|
+
tupleDimensions: tuple[int, ...]
|
|
35
|
+
dimensionsTuple
|
|
36
|
+
dimensionTuple
|
|
37
|
+
"""
|
|
63
38
|
|
|
64
39
|
def getPathSyntheticModules() -> Path:
|
|
65
|
-
|
|
66
|
-
return pathSyntheticModules
|
|
67
|
-
|
|
68
|
-
def getAlgorithmSource() -> ModuleType:
|
|
69
|
-
from mapFolding import theDao
|
|
70
|
-
return theDao
|
|
40
|
+
return pathPackage / moduleOfSyntheticModules
|
|
71
41
|
|
|
72
42
|
def getAlgorithmDispatcher() -> Callable[..., None]:
|
|
73
|
-
algorithmSource = getAlgorithmSource()
|
|
43
|
+
algorithmSource: ModuleType = getAlgorithmSource()
|
|
74
44
|
return cast(Callable[..., None], algorithmSource.doTheNeedful) # 'doTheNeedful' is duplicated and there is not a SSOT for it
|
|
75
45
|
|
|
76
|
-
def getDispatcherCallable() -> Callable[..., None]:
|
|
77
|
-
from mapFolding.syntheticModules import numba_doTheNeedful
|
|
78
|
-
return cast(Callable[..., None], numba_doTheNeedful.doTheNeedful)
|
|
79
|
-
|
|
80
46
|
# NOTE I want this _concept_, not necessarily this method, to be well implemented and usable everywhere: Python, Numba, Jax, CUDA, idc
|
|
81
47
|
class computationState(TypedDict):
|
|
82
48
|
connectionGraph: ndarray[tuple[int, int, int], dtype[integer[Any]]]
|
|
@@ -133,7 +99,7 @@ class ParametersNumba(TypedDict):
|
|
|
133
99
|
nopython: bool
|
|
134
100
|
parallel: bool
|
|
135
101
|
pipeline_class: NotRequired[type[numbaCompilerBase]]
|
|
136
|
-
signature_or_function: NotRequired[Any | Callable | str | tuple]
|
|
102
|
+
signature_or_function: NotRequired[Any | Callable[..., Any] | str | tuple[Any, ...]]
|
|
137
103
|
target: NotRequired[str]
|
|
138
104
|
|
|
139
105
|
parametersNumbaFailEarly: Final[ParametersNumba] = { '_nrt': True, 'boundscheck': True, 'cache': True, 'error_model': 'python', 'fastmath': False, 'forceinline': True, 'inline': 'always', 'looplift': False, 'no_cfunc_wrapper': False, 'no_cpython_wrapper': False, 'nopython': True, 'parallel': False, }
|
mapFolding/theSSOTdatatypes.py
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
|
+
from mapFolding.theConfiguration import *
|
|
1
2
|
from collections import defaultdict
|
|
2
|
-
from typing import Any, cast, Final
|
|
3
|
+
from typing import Any, cast, Final
|
|
3
4
|
import enum
|
|
4
|
-
import numba
|
|
5
|
-
import numpy
|
|
6
|
-
|
|
7
|
-
try:
|
|
8
|
-
from typing import NotRequired
|
|
9
|
-
except Exception:
|
|
10
|
-
from typing_extensions import NotRequired # type: ignore
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from typing import TypedDict
|
|
14
|
-
else:
|
|
15
|
-
TypedDict = dict
|
|
5
|
+
import numba # type: ignore
|
|
6
|
+
import numpy # type: ignore
|
|
16
7
|
|
|
17
8
|
class EnumIndices(enum.IntEnum):
|
|
18
9
|
@staticmethod
|
|
@@ -51,20 +42,18 @@ _datatypeDefault: Final[dict[str, str]] = {
|
|
|
51
42
|
'foldsTotal': 'int64',
|
|
52
43
|
'leavesTotal': 'uint16',
|
|
53
44
|
}
|
|
54
|
-
_datatypeModule = ''
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
_datatype: dict[str, str] = defaultdict(str)
|
|
45
|
+
_datatypeModule: str = ''
|
|
46
|
+
_registryOfDatatypes: dict[str, str] = defaultdict(str)
|
|
58
47
|
|
|
59
48
|
def reportDatatypeLimit(identifier: str, datatype: str, sourGrapes: bool | None = False) -> str:
|
|
60
|
-
global
|
|
61
|
-
if not
|
|
62
|
-
|
|
63
|
-
elif
|
|
49
|
+
global _registryOfDatatypes
|
|
50
|
+
if not _registryOfDatatypes[identifier]:
|
|
51
|
+
_registryOfDatatypes[identifier] = datatype
|
|
52
|
+
elif _registryOfDatatypes[identifier] == datatype:
|
|
64
53
|
pass
|
|
65
54
|
elif sourGrapes:
|
|
66
|
-
raise Exception(f"Datatype is '{
|
|
67
|
-
return
|
|
55
|
+
raise Exception(f"Datatype is '{_registryOfDatatypes[identifier]}' not '{datatype}', so you can take your ball and go home.")
|
|
56
|
+
return _registryOfDatatypes[identifier]
|
|
68
57
|
|
|
69
58
|
def setDatatypeModule(datatypeModule: str, sourGrapes: bool | None = False) -> str:
|
|
70
59
|
global _datatypeModule
|
|
@@ -86,29 +75,29 @@ def setDatatypeLeavesTotal(datatype: str, sourGrapes: bool | None = False) -> st
|
|
|
86
75
|
return reportDatatypeLimit('leavesTotal', datatype, sourGrapes)
|
|
87
76
|
|
|
88
77
|
def _get_datatype(identifier: str) -> str:
|
|
89
|
-
global
|
|
90
|
-
if not
|
|
78
|
+
global _registryOfDatatypes
|
|
79
|
+
if not _registryOfDatatypes[identifier]:
|
|
91
80
|
if identifier in indexMy._member_names_:
|
|
92
|
-
|
|
81
|
+
_registryOfDatatypes[identifier] = _datatypeDefault.get(identifier) or _get_datatype('elephino')
|
|
93
82
|
elif identifier in indexTrack._member_names_:
|
|
94
|
-
|
|
83
|
+
_registryOfDatatypes[identifier] = _datatypeDefault.get(identifier) or _get_datatype('elephino')
|
|
95
84
|
else:
|
|
96
|
-
|
|
97
|
-
return
|
|
85
|
+
_registryOfDatatypes[identifier] = _datatypeDefault.get(identifier) or _get_datatype('foldsTotal')
|
|
86
|
+
return _registryOfDatatypes[identifier]
|
|
98
87
|
|
|
99
88
|
def getDatatypeModule() -> str:
|
|
100
89
|
global _datatypeModule
|
|
101
90
|
if not _datatypeModule:
|
|
102
|
-
_datatypeModule =
|
|
91
|
+
_datatypeModule = datatypeModulePACKAGING
|
|
103
92
|
return _datatypeModule
|
|
104
93
|
|
|
105
94
|
def setInStone(identifier: str) -> type[Any]:
|
|
106
|
-
datatypeModule = getDatatypeModule()
|
|
107
|
-
datatypeStr = _get_datatype(identifier)
|
|
95
|
+
datatypeModule: str = getDatatypeModule()
|
|
96
|
+
datatypeStr: str = _get_datatype(identifier)
|
|
108
97
|
return cast(type[Any], getattr(eval(datatypeModule), datatypeStr))
|
|
109
98
|
|
|
110
99
|
def hackSSOTdtype(identifier: str) -> type[Any]:
|
|
111
|
-
_hackSSOTdtype={
|
|
100
|
+
_hackSSOTdtype: dict[str, str]={
|
|
112
101
|
'connectionGraph': 'dtypeLeavesTotal',
|
|
113
102
|
'dtypeElephino': 'dtypeElephino',
|
|
114
103
|
'dtypeFoldsTotal': 'dtypeFoldsTotal',
|
|
@@ -119,7 +108,7 @@ def hackSSOTdtype(identifier: str) -> type[Any]:
|
|
|
119
108
|
'my': 'dtypeElephino',
|
|
120
109
|
'track': 'dtypeElephino',
|
|
121
110
|
}
|
|
122
|
-
RubeGoldBerg = _hackSSOTdtype[identifier]
|
|
111
|
+
RubeGoldBerg: str = _hackSSOTdtype[identifier]
|
|
123
112
|
if RubeGoldBerg == 'dtypeElephino':
|
|
124
113
|
return setInStone('elephino')
|
|
125
114
|
elif RubeGoldBerg == 'dtypeFoldsTotal':
|
|
@@ -129,7 +118,7 @@ def hackSSOTdtype(identifier: str) -> type[Any]:
|
|
|
129
118
|
raise Exception("Dude, you forgot to set a value in `hackSSOTdtype`.")
|
|
130
119
|
|
|
131
120
|
def hackSSOTdatatype(identifier: str) -> str:
|
|
132
|
-
_hackSSOTdatatype={
|
|
121
|
+
_hackSSOTdatatype: dict[str, str]={
|
|
133
122
|
'connectionGraph': 'datatypeLeavesTotal',
|
|
134
123
|
'countDimensionsGapped': 'datatypeLeavesTotal',
|
|
135
124
|
'datatypeElephino': 'datatypeElephino',
|
|
@@ -156,7 +145,7 @@ def hackSSOTdatatype(identifier: str) -> str:
|
|
|
156
145
|
'taskIndex': 'datatypeLeavesTotal',
|
|
157
146
|
'track': 'datatypeElephino',
|
|
158
147
|
}
|
|
159
|
-
RubeGoldBerg = _hackSSOTdatatype[identifier]
|
|
148
|
+
RubeGoldBerg: str = _hackSSOTdatatype[identifier]
|
|
160
149
|
if RubeGoldBerg == 'datatypeElephino':
|
|
161
150
|
return _get_datatype('elephino')
|
|
162
151
|
elif RubeGoldBerg == 'datatypeFoldsTotal':
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
|
|
3
|
+
datatypeModulePACKAGING: Final[str] = 'numpy'
|
|
4
|
+
myPackageNameIsPACKAGING: str = "mapFolding"
|
|
5
|
+
listCallablesDispatcheesHARDCODED: list[str] = ['countInitialize', 'countParallel', 'countSequential']
|
|
6
|
+
additional_importsHARDCODED: list[str] = ['numba']
|
|
7
|
+
algorithmSourcePACKAGING: str = 'theDao'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mapFolding
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Count distinct ways to fold a map (or a strip of stamps)
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License: CC-BY-NC-4.0
|
|
@@ -16,12 +16,12 @@ Classifier: Intended Audience :: Other Audience
|
|
|
16
16
|
Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
21
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
Classifier: Programming Language :: Python :: 3
|
|
24
|
-
Classifier: Programming Language :: Python
|
|
25
25
|
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
26
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
27
|
Classifier: Typing :: Typed
|
|
@@ -33,7 +33,9 @@ Requires-Dist: numpy
|
|
|
33
33
|
Requires-Dist: Z0Z_tools
|
|
34
34
|
Provides-Extra: testing
|
|
35
35
|
Requires-Dist: autoflake; extra == "testing"
|
|
36
|
+
Requires-Dist: mypy; extra == "testing"
|
|
36
37
|
Requires-Dist: more_itertools; extra == "testing"
|
|
38
|
+
Requires-Dist: numba_progress; extra == "testing"
|
|
37
39
|
Requires-Dist: pytest-cov; extra == "testing"
|
|
38
40
|
Requires-Dist: pytest-env; extra == "testing"
|
|
39
41
|
Requires-Dist: pytest-xdist; extra == "testing"
|
|
@@ -152,4 +154,4 @@ Available OEIS sequences:
|
|
|
152
154
|
[](https://HunterThinks.com/support)
|
|
153
155
|
[](https://www.youtube.com/@HunterHogan)
|
|
154
156
|
|
|
155
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=pggwv9wBf1jqZUxp8qO1Si1LswyTGZQLoRqdO0ptiS4,2707
|
|
2
|
+
mapFolding/basecamp.py,sha256=Y2yBE89IviRcVOvLM1mRWocqPHVkmZ2awAZVkDTDTAo,3571
|
|
3
|
+
mapFolding/beDRY.py,sha256=5D-9AwbtBULTQ4N5WX57ZXqw6mCmPa31eCU2xpQXlf4,15750
|
|
4
|
+
mapFolding/oeis.py,sha256=qxNrmEfx5IYc5tw312J3Jk3xO-nAeaM8unxmxM7R8Cs,11924
|
|
5
|
+
mapFolding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mapFolding/theConfiguration.py,sha256=vKLwflVNzthbZlOWTwEC4xqh0rp9Xoyz-hUAT7a5CSA,2098
|
|
7
|
+
mapFolding/theDao.py,sha256=A-cDyixGLROAehYxf4k0srVjoe4aSeXJK_k40irfndU,12626
|
|
8
|
+
mapFolding/theSSOT.py,sha256=55sy05nmAaPpDAiuA6t27u20obHMzY8NTD_XR2Ffgos,4940
|
|
9
|
+
mapFolding/theSSOTdatatypes.py,sha256=V5G99QwMMaZV-J3EIPAKIKorV3fz0LHUgwErF9MP01E,5854
|
|
10
|
+
mapFolding/theWrongWay.py,sha256=p7nL3JM9t4mbF1xBn1gZzG1jGs9KRkyGP3XliUPkm-4,312
|
|
11
|
+
mapfolding-0.6.0.dist-info/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
12
|
+
mapfolding-0.6.0.dist-info/METADATA,sha256=cnO-gz66V3NnAdUIAgKKIsT8PgVxqD4I726HqgYVGSc,7768
|
|
13
|
+
mapfolding-0.6.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
14
|
+
mapfolding-0.6.0.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
15
|
+
mapfolding-0.6.0.dist-info/top_level.txt,sha256=aG3bjFBoxxuaV3Iu1wZAd241Ubs3cdaJtKYBQBDIjsg,11
|
|
16
|
+
mapfolding-0.6.0.dist-info/RECORD,,
|
|
@@ -1,377 +0,0 @@
|
|
|
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
|
-
dtypeMedium = 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
|
-
taskDivisions = 0
|
|
271
|
-
if isinstance(computationDivisions, int):
|
|
272
|
-
taskDivisions = computationDivisions
|
|
273
|
-
elif isinstance(computationDivisions, str):
|
|
274
|
-
computationDivisions = computationDivisions.lower()
|
|
275
|
-
if computationDivisions == "maximum":
|
|
276
|
-
taskDivisions = leavesTotal
|
|
277
|
-
elif computationDivisions == "cpu":
|
|
278
|
-
taskDivisions = min(concurrencyLimit, leavesTotal)
|
|
279
|
-
else:
|
|
280
|
-
raise ValueError("Not my problem.")
|
|
281
|
-
|
|
282
|
-
if taskDivisions > leavesTotal:
|
|
283
|
-
raise ValueError("What are you doing?")
|
|
284
|
-
|
|
285
|
-
return taskDivisions
|
|
286
|
-
|
|
287
|
-
def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optional[Type]) -> NDArray[integer[Any]]:
|
|
288
|
-
datatype = keywordArguments.get('datatype', dtypeMedium)
|
|
289
|
-
mapShape = validateListDimensions(listDimensions)
|
|
290
|
-
leavesTotal = getLeavesTotal(mapShape)
|
|
291
|
-
arrayDimensions = numpy.array(mapShape, dtype=datatype)
|
|
292
|
-
dimensionsTotal = len(arrayDimensions)
|
|
293
|
-
|
|
294
|
-
cumulativeProduct = numpy.multiply.accumulate([1] + mapShape, dtype=datatype)
|
|
295
|
-
coordinateSystem = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1), dtype=datatype)
|
|
296
|
-
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
297
|
-
for leaf1ndex in range(1, leavesTotal + 1):
|
|
298
|
-
coordinateSystem[dimension1ndex, leaf1ndex] = ( ((leaf1ndex - 1) // cumulativeProduct[dimension1ndex - 1]) % arrayDimensions[dimension1ndex - 1] + 1 )
|
|
299
|
-
|
|
300
|
-
connectionGraph = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1), dtype=datatype)
|
|
301
|
-
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
302
|
-
for activeLeaf1ndex in range(1, leavesTotal + 1):
|
|
303
|
-
for connectee1ndex in range(1, activeLeaf1ndex + 1):
|
|
304
|
-
isFirstCoord = coordinateSystem[dimension1ndex, connectee1ndex] == 1
|
|
305
|
-
isLastCoord = coordinateSystem[dimension1ndex, connectee1ndex] == arrayDimensions[dimension1ndex - 1]
|
|
306
|
-
exceedsActive = connectee1ndex + cumulativeProduct[dimension1ndex - 1] > activeLeaf1ndex
|
|
307
|
-
isEvenParity = (coordinateSystem[dimension1ndex, activeLeaf1ndex] & 1) == (coordinateSystem[dimension1ndex, connectee1ndex] & 1)
|
|
308
|
-
|
|
309
|
-
if (isEvenParity and isFirstCoord) or (not isEvenParity and (isLastCoord or exceedsActive)):
|
|
310
|
-
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
311
|
-
elif isEvenParity and not isFirstCoord:
|
|
312
|
-
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex - cumulativeProduct[dimension1ndex - 1]
|
|
313
|
-
elif not isEvenParity and not (isLastCoord or exceedsActive):
|
|
314
|
-
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex + cumulativeProduct[dimension1ndex - 1]
|
|
315
|
-
else:
|
|
316
|
-
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
317
|
-
return connectionGraph
|
|
318
|
-
|
|
319
|
-
def makeDataContainer(shape, datatype: Optional[Type] = None):
|
|
320
|
-
if datatype is None:
|
|
321
|
-
datatype = dtypeMedium
|
|
322
|
-
return numpy.zeros(shape, dtype=datatype)
|
|
323
|
-
|
|
324
|
-
def outfitFoldings(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Type]) -> computationState:
|
|
325
|
-
datatypeMedium = keywordArguments.get('datatypeMedium', dtypeMedium)
|
|
326
|
-
datatypeLarge = keywordArguments.get('datatypeLarge', dtypeLarge)
|
|
327
|
-
|
|
328
|
-
the = makeDataContainer(len(indexThe), datatypeMedium)
|
|
329
|
-
|
|
330
|
-
mapShape = tuple(sorted(validateListDimensions(listDimensions)))
|
|
331
|
-
the[indexThe.leavesTotal] = getLeavesTotal(mapShape)
|
|
332
|
-
the[indexThe.dimensionsTotal] = len(mapShape)
|
|
333
|
-
concurrencyLimit = setCPUlimit(CPUlimit)
|
|
334
|
-
the[indexThe.taskDivisions] = getTaskDivisions(computationDivisions, concurrencyLimit, CPUlimit, listDimensions)
|
|
335
|
-
|
|
336
|
-
stateInitialized = computationState(
|
|
337
|
-
connectionGraph = makeConnectionGraph(mapShape, datatype=datatypeMedium),
|
|
338
|
-
foldsSubTotals = makeDataContainer(the[indexThe.leavesTotal], datatypeLarge),
|
|
339
|
-
mapShape = mapShape,
|
|
340
|
-
my = makeDataContainer(len(indexMy), datatypeLarge),
|
|
341
|
-
gapsWhere = makeDataContainer(int(the[indexThe.leavesTotal]) * int(the[indexThe.leavesTotal]) + 1, datatypeMedium),
|
|
342
|
-
the = the,
|
|
343
|
-
track = makeDataContainer((len(indexTrack), the[indexThe.leavesTotal] + 1), datatypeLarge)
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
stateInitialized['my'][indexMy.leaf1ndex] = 1
|
|
347
|
-
return stateInitialized
|
|
348
|
-
|
|
349
|
-
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'unnamed parameter') -> List[int]:
|
|
350
|
-
# listValidated = intInnit(dimensions, parameterName)
|
|
351
|
-
listNOTValidated = dimensions if isinstance(dimensions, (list, tuple)) else list(dimensions)
|
|
352
|
-
listNonNegative = []
|
|
353
|
-
for dimension in listNOTValidated:
|
|
354
|
-
if dimension < 0:
|
|
355
|
-
raise ValueError(f"Dimension {dimension} must be non-negative")
|
|
356
|
-
listNonNegative.append(dimension)
|
|
357
|
-
if not listNonNegative:
|
|
358
|
-
raise ValueError("At least one dimension must be non-negative")
|
|
359
|
-
return listNonNegative
|
|
360
|
-
|
|
361
|
-
def setCPUlimit(CPUlimit: Union[bool, float, int, None]) -> int:
|
|
362
|
-
# if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
363
|
-
# CPUlimit = oopsieKwargsie(CPUlimit)
|
|
364
|
-
# concurrencyLimit = defineConcurrencyLimit(CPUlimit)
|
|
365
|
-
# numba.set_num_threads(concurrencyLimit)
|
|
366
|
-
concurrencyLimitHARDCODED = 1
|
|
367
|
-
concurrencyLimit = concurrencyLimitHARDCODED
|
|
368
|
-
return concurrencyLimit
|
|
369
|
-
|
|
370
|
-
def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
|
|
371
|
-
if not listDimensions:
|
|
372
|
-
raise ValueError(f"listDimensions is a required parameter.")
|
|
373
|
-
listNonNegative = parseDimensions(listDimensions, 'listDimensions')
|
|
374
|
-
dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
|
|
375
|
-
if len(dimensionsValid) < 2:
|
|
376
|
-
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/.")
|
|
377
|
-
return sorted(dimensionsValid)
|