mapFolding 0.4.3__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 +1 -1
- mapFolding/basecamp.py +11 -13
- mapFolding/beDRY.py +21 -20
- mapFolding/oeis.py +21 -20
- mapFolding/someAssemblyRequired/makeJob.py +8 -7
- mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +1 -3
- mapFolding/someAssemblyRequired/synthesizeNumba.py +19 -14
- mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +22 -21
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +6 -13
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +9 -15
- mapFolding/syntheticModules/numbaCount.py +6 -6
- mapFolding/syntheticModules/numba_doTheNeedful.py +4 -4
- mapFolding/theDao.py +48 -48
- mapFolding/theSSOT.py +35 -21
- mapFolding/theSSOTdatatypes.py +13 -15
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.0.dist-info}/METADATA +2 -1
- mapFolding-0.5.0.dist-info/RECORD +39 -0
- tests/conftest.py +56 -27
- tests/test_computations.py +5 -6
- tests/test_oeis.py +1 -2
- tests/test_other.py +11 -7
- tests/test_tasks.py +5 -5
- mapFolding-0.4.3.dist-info/RECORD +0 -40
- tests/test_types.py +0 -5
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.0.dist-info}/LICENSE +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.0.dist-info}/WHEEL +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.0.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.4.3.dist-info → mapFolding-0.5.0.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py
CHANGED
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
|
|
|
@@ -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
|
|
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
|
|
|
@@ -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
|
|
|
@@ -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 = 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,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
|
|
27
|
-
getMapShape: Callable[[int],
|
|
29
|
+
getMapShape: Callable[[int], list[int]]
|
|
28
30
|
offset: int
|
|
29
|
-
valuesBenchmark:
|
|
30
|
-
valuesKnown:
|
|
31
|
-
valuesTestParallelization:
|
|
32
|
-
valuesTestValidation:
|
|
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,7 +67,7 @@ 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
73
|
def validateOEISid(oeisIDcandidate: str) -> str:
|
|
@@ -102,7 +104,7 @@ def getFilenameOEISbFile(oeisID: str) -> str:
|
|
|
102
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.
|
|
108
110
|
|
|
@@ -135,7 +137,6 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
|
|
|
135
137
|
return OEISsequence
|
|
136
138
|
|
|
137
139
|
def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
138
|
-
cacheDays = 7
|
|
139
140
|
tryCache = False
|
|
140
141
|
if pathFilenameCache.exists():
|
|
141
142
|
fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
|
|
@@ -145,7 +146,7 @@ def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
|
145
146
|
if tryCache:
|
|
146
147
|
try:
|
|
147
148
|
oeisInformation = pathFilenameCache.read_text()
|
|
148
|
-
except
|
|
149
|
+
except OSError:
|
|
149
150
|
tryCache = False
|
|
150
151
|
|
|
151
152
|
if not tryCache:
|
|
@@ -159,7 +160,7 @@ def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
|
|
|
159
160
|
|
|
160
161
|
return oeisInformation
|
|
161
162
|
|
|
162
|
-
def getOEISidValues(oeisID: str) ->
|
|
163
|
+
def getOEISidValues(oeisID: str) -> dict[int, int]:
|
|
163
164
|
"""
|
|
164
165
|
Retrieves the specified OEIS sequence as a dictionary mapping integer indices
|
|
165
166
|
to their corresponding values.
|
|
@@ -187,7 +188,7 @@ def getOEISidValues(oeisID: str) -> Dict[int, int]:
|
|
|
187
188
|
return _parseBFileOEIS(oeisInformation, oeisID)
|
|
188
189
|
return {-1: -1}
|
|
189
190
|
|
|
190
|
-
def getOEISidInformation(oeisID: str) ->
|
|
191
|
+
def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
191
192
|
oeisID = validateOEISid(oeisID)
|
|
192
193
|
pathFilenameCache = _pathCache / f"{oeisID}.txt"
|
|
193
194
|
url = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
|
|
@@ -219,7 +220,7 @@ def getOEISidInformation(oeisID: str) -> Tuple[str, int]:
|
|
|
219
220
|
description = ' '.join(description_parts)
|
|
220
221
|
return description, offset
|
|
221
222
|
|
|
222
|
-
def makeSettingsOEIS() ->
|
|
223
|
+
def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
|
|
223
224
|
settingsTarget = {}
|
|
224
225
|
for oeisID in oeisIDsImplemented:
|
|
225
226
|
valuesKnownSherpa = getOEISidValues(oeisID)
|
|
@@ -236,7 +237,7 @@ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
|
|
|
236
237
|
)
|
|
237
238
|
return settingsTarget
|
|
238
239
|
|
|
239
|
-
settingsOEIS:
|
|
240
|
+
settingsOEIS: dict[str, SettingsOEIS] = makeSettingsOEIS()
|
|
240
241
|
"""All values and settings for `oeisIDsImplemented`."""
|
|
241
242
|
|
|
242
243
|
"""
|
|
@@ -288,7 +289,7 @@ def oeisIDfor_n(oeisID: str, n: int) -> int:
|
|
|
288
289
|
if not isinstance(n, int) or n < 0:
|
|
289
290
|
raise ValueError("`n` must be non-negative integer.")
|
|
290
291
|
|
|
291
|
-
listDimensions = settingsOEIS[oeisID]['getMapShape'](n)
|
|
292
|
+
listDimensions: list[int] = settingsOEIS[oeisID]['getMapShape'](n)
|
|
292
293
|
|
|
293
294
|
if n <= 1 or len(listDimensions) < 2:
|
|
294
295
|
offset = settingsOEIS[oeisID]['offset']
|
|
@@ -309,9 +310,9 @@ def OEIS_for_n() -> None:
|
|
|
309
310
|
parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
|
|
310
311
|
parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
|
|
311
312
|
|
|
312
|
-
argumentsCLI = parserCLI.parse_args()
|
|
313
|
+
argumentsCLI: argparse.Namespace = parserCLI.parse_args()
|
|
313
314
|
|
|
314
|
-
timeStart = time.perf_counter()
|
|
315
|
+
timeStart: float = time.perf_counter()
|
|
315
316
|
|
|
316
317
|
try:
|
|
317
318
|
print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
|
|
@@ -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)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
TODO: consolidate the logic in this module."""
|
|
3
3
|
from mapFolding.someAssemblyRequired.synthesizeNumbaGeneralized import *
|
|
4
4
|
|
|
5
|
-
def insertArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker, unrollSlices:
|
|
5
|
+
def insertArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker, unrollSlices: int | None = None) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
6
6
|
arrayType = type(arrayTarget)
|
|
7
7
|
moduleConstructor = arrayType.__module__
|
|
8
8
|
constructorName = arrayType.__name__
|
|
@@ -34,7 +34,7 @@ def insertArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arra
|
|
|
34
34
|
|
|
35
35
|
return FunctionDefTarget, allImports
|
|
36
36
|
|
|
37
|
-
def findAndReplaceTrackArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str , arrayTarget: numpy.ndarray , allImports: UniversalImportTracker) ->
|
|
37
|
+
def findAndReplaceTrackArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str , arrayTarget: numpy.ndarray , allImports: UniversalImportTracker) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
38
38
|
|
|
39
39
|
arrayType = type(arrayTarget)
|
|
40
40
|
moduleConstructor = arrayType.__module__
|
|
@@ -62,7 +62,7 @@ def findAndReplaceTrackArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifi
|
|
|
62
62
|
allImports.addImportFromStr(moduleConstructor, datatypeName, dtypeAsName)
|
|
63
63
|
return FunctionDefTarget, allImports
|
|
64
64
|
|
|
65
|
-
def findAndReplaceArraySubscriptIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, Z0Z_listChaff:
|
|
65
|
+
def findAndReplaceArraySubscriptIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, Z0Z_listChaff: list[str], allImports: UniversalImportTracker) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
66
66
|
moduleConstructor = Z0Z_getDatatypeModuleScalar()
|
|
67
67
|
for statement in FunctionDefTarget.body.copy():
|
|
68
68
|
if ifThis.isUnpackingAnArray(identifier)(statement):
|
|
@@ -87,7 +87,7 @@ def removeAssignTargetFrom_body(FunctionDefTarget: ast.FunctionDef, identifier:
|
|
|
87
87
|
return False
|
|
88
88
|
targetNode = astNode.targets[0]
|
|
89
89
|
return (isinstance(targetNode, ast.Subscript) and isinstance(targetNode.value, ast.Name) and targetNode.value.id == identifier) or ifThis.nameIs(identifier)(targetNode)
|
|
90
|
-
def replacementBuilder(astNode: ast.AST) ->
|
|
90
|
+
def replacementBuilder(astNode: ast.AST) -> ast.stmt | None:
|
|
91
91
|
# Returning None removes the node.
|
|
92
92
|
return None
|
|
93
93
|
FunctionDefSherpa = NodeReplacer(predicate, replacementBuilder).visit(FunctionDefTarget)
|
|
@@ -98,7 +98,7 @@ def removeAssignTargetFrom_body(FunctionDefTarget: ast.FunctionDef, identifier:
|
|
|
98
98
|
ast.fix_missing_locations(FunctionDefTarget)
|
|
99
99
|
return FunctionDefTarget
|
|
100
100
|
|
|
101
|
-
def findAndReplaceAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker) ->
|
|
101
|
+
def findAndReplaceAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
102
102
|
moduleConstructor = Z0Z_getDatatypeModuleScalar()
|
|
103
103
|
for stmt in FunctionDefTarget.body.copy():
|
|
104
104
|
if isinstance(stmt, ast.AnnAssign):
|
|
@@ -137,7 +137,7 @@ def findAstNameReplaceWithConstantIn_body(FunctionDefTarget: ast.FunctionDef, na
|
|
|
137
137
|
|
|
138
138
|
return cast(ast.FunctionDef, NodeReplacer(ifThis.nameIs(name), replaceWithConstant).visit(FunctionDefTarget))
|
|
139
139
|
|
|
140
|
-
def insertReturnStatementIn_body(FunctionDefTarget: ast.FunctionDef, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker) ->
|
|
140
|
+
def insertReturnStatementIn_body(FunctionDefTarget: ast.FunctionDef, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
141
141
|
"""Add multiplication and return statement to function, properly constructing AST nodes."""
|
|
142
142
|
# Create AST for multiplication operation
|
|
143
143
|
multiplicand = Z0Z_identifierCountFolds
|
|
@@ -157,7 +157,7 @@ def insertReturnStatementIn_body(FunctionDefTarget: ast.FunctionDef, arrayTarget
|
|
|
157
157
|
|
|
158
158
|
return FunctionDefTarget, allImports
|
|
159
159
|
|
|
160
|
-
def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int) ->
|
|
160
|
+
def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int) -> ast.FunctionDef:
|
|
161
161
|
"""
|
|
162
162
|
Unroll all nested while loops matching the condition that their test uses `iteratorName`.
|
|
163
163
|
"""
|
|
@@ -180,11 +180,11 @@ def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorN
|
|
|
180
180
|
self.iteratorName = iteratorName
|
|
181
181
|
self.iterationsTotal = iterationsTotal
|
|
182
182
|
|
|
183
|
-
def visit_While(self, node: ast.While) ->
|
|
183
|
+
def visit_While(self, node: ast.While) -> list[ast.stmt]:
|
|
184
184
|
# Check if the while loop's test uses the iterator.
|
|
185
185
|
if isinstance(node.test, ast.Compare) and ifThis.nameIs(self.iteratorName)(node.test.left):
|
|
186
186
|
# Recurse the while loop body and remove AugAssign that increments the iterator.
|
|
187
|
-
cleanBodyStatements:
|
|
187
|
+
cleanBodyStatements: list[ast.stmt] = []
|
|
188
188
|
for loopStatement in node.body:
|
|
189
189
|
# Recursively visit nested statements.
|
|
190
190
|
visitedStatement = self.visit(loopStatement)
|
|
@@ -198,7 +198,7 @@ def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorN
|
|
|
198
198
|
continue
|
|
199
199
|
cleanBodyStatements.append(visitedStatement)
|
|
200
200
|
|
|
201
|
-
newStatements:
|
|
201
|
+
newStatements: list[ast.stmt] = []
|
|
202
202
|
# Unroll using the filtered body.
|
|
203
203
|
for iterationIndex in range(self.iterationsTotal):
|
|
204
204
|
for loopStatement in cleanBodyStatements:
|
|
@@ -222,7 +222,7 @@ def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorN
|
|
|
222
222
|
ast.fix_missing_locations(newFunctionDef)
|
|
223
223
|
return newFunctionDef
|
|
224
224
|
|
|
225
|
-
def makeLauncherBasicJobNumba(callableTarget: str, pathFilenameFoldsTotal:
|
|
225
|
+
def makeLauncherBasicJobNumba(callableTarget: str, pathFilenameFoldsTotal: Path) -> ast.Module:
|
|
226
226
|
linesLaunch = f"""
|
|
227
227
|
if __name__ == '__main__':
|
|
228
228
|
import time
|
|
@@ -235,7 +235,12 @@ if __name__ == '__main__':
|
|
|
235
235
|
"""
|
|
236
236
|
return ast.parse(linesLaunch)
|
|
237
237
|
|
|
238
|
-
def makeFunctionDef(astModule: ast.Module,
|
|
238
|
+
def makeFunctionDef(astModule: ast.Module,
|
|
239
|
+
callableTarget: str,
|
|
240
|
+
parametersNumba: ParametersNumba | None = None,
|
|
241
|
+
inlineCallables: bool | None = False,
|
|
242
|
+
unpackArrays: bool | None = False,
|
|
243
|
+
allImports: UniversalImportTracker | None = None) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
239
244
|
if allImports is None:
|
|
240
245
|
allImports = UniversalImportTracker()
|
|
241
246
|
for statement in astModule.body:
|
|
@@ -266,7 +271,7 @@ def makeFunctionDef(astModule: ast.Module, callableTarget: str, parametersNumba:
|
|
|
266
271
|
|
|
267
272
|
return FunctionDefTarget, allImports
|
|
268
273
|
|
|
269
|
-
def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba:
|
|
274
|
+
def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: ParametersNumba | None = None) -> tuple[ast.FunctionDef, UniversalImportTracker]:
|
|
270
275
|
def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
|
|
271
276
|
# TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
|
|
272
277
|
for decoratorItem in astCallable.decorator_list.copy():
|
|
@@ -303,7 +308,7 @@ def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: Un
|
|
|
303
308
|
datatypeModuleDecorator = Z0Z_getDatatypeModuleScalar()
|
|
304
309
|
list_argsDecorator: Sequence[ast.expr] = []
|
|
305
310
|
|
|
306
|
-
list_arg4signature_or_function:
|
|
311
|
+
list_arg4signature_or_function: list[ast.expr] = []
|
|
307
312
|
for parameter in FunctionDefTarget.args.args:
|
|
308
313
|
signatureElement = make_arg4parameter(parameter)
|
|
309
314
|
if signatureElement:
|
|
@@ -35,7 +35,8 @@ from mapFolding.someAssemblyRequired.makeJob import makeStateJob
|
|
|
35
35
|
from numpy import integer
|
|
36
36
|
from numpy.typing import NDArray
|
|
37
37
|
from types import ModuleType
|
|
38
|
-
from
|
|
38
|
+
from collections.abc import Callable, Sequence
|
|
39
|
+
from typing import Any, cast
|
|
39
40
|
from Z0Z_tools import autoDecodingRLE, updateExtendPolishDictionaryLists
|
|
40
41
|
import ast
|
|
41
42
|
import autoflake
|
|
@@ -46,8 +47,8 @@ import inspect
|
|
|
46
47
|
import more_itertools
|
|
47
48
|
import numba
|
|
48
49
|
import numpy
|
|
49
|
-
import
|
|
50
|
-
import
|
|
50
|
+
from os import PathLike
|
|
51
|
+
from pathlib import Path
|
|
51
52
|
import python_minifier
|
|
52
53
|
import warnings
|
|
53
54
|
|
|
@@ -63,8 +64,8 @@ class ASTBodyTransformer:
|
|
|
63
64
|
def __init__(self, functionDefinition: ast.FunctionDef) -> None:
|
|
64
65
|
self.functionDefinition = functionDefinition
|
|
65
66
|
|
|
66
|
-
def replaceIn_body(self, predicate: Callable[[ast.stmt], bool], replacementBuilder: Callable[[ast.stmt],
|
|
67
|
-
newBody:
|
|
67
|
+
def replaceIn_body(self, predicate: Callable[[ast.stmt], bool], replacementBuilder: Callable[[ast.stmt], ast.stmt | None]) -> None:
|
|
68
|
+
newBody: list[ast.stmt] = []
|
|
68
69
|
for statement in self.functionDefinition.body:
|
|
69
70
|
if predicate(statement):
|
|
70
71
|
replacementStatement = replacementBuilder(statement)
|
|
@@ -127,16 +128,16 @@ class ifThis:
|
|
|
127
128
|
class Then:
|
|
128
129
|
"""Generic actions."""
|
|
129
130
|
@staticmethod
|
|
130
|
-
def copy_astCallKeywords(astCall: ast.Call) ->
|
|
131
|
+
def copy_astCallKeywords(astCall: ast.Call) -> dict[str, Any]:
|
|
131
132
|
"""Extract keyword parameters from a decorator AST node."""
|
|
132
|
-
dictionaryKeywords:
|
|
133
|
+
dictionaryKeywords: dict[str, Any] = {}
|
|
133
134
|
for keywordItem in astCall.keywords:
|
|
134
135
|
if isinstance(keywordItem.value, ast.Constant) and keywordItem.arg is not None:
|
|
135
136
|
dictionaryKeywords[keywordItem.arg] = keywordItem.value.value
|
|
136
137
|
return dictionaryKeywords
|
|
137
138
|
|
|
138
139
|
@staticmethod
|
|
139
|
-
def make_astCall(name: str, args:
|
|
140
|
+
def make_astCall(name: str, args: Sequence[ast.expr] | None = None, list_astKeywords: Sequence[ast.keyword] | None = None, dictionaryKeywords: dict[str, Any] | None = None) -> ast.Call:
|
|
140
141
|
list_dictionaryKeywords = [ast.keyword(arg=keyName, value=ast.Constant(value=keyValue)) for keyName, keyValue in dictionaryKeywords.items()] if dictionaryKeywords else []
|
|
141
142
|
return ast.Call(
|
|
142
143
|
func=ast.Name(id=name, ctx=ast.Load()),
|
|
@@ -159,7 +160,7 @@ class NodeReplacer(ast.NodeTransformer):
|
|
|
159
160
|
visit(node: ast.AST) -> Optional[ast.AST]:
|
|
160
161
|
Visits each node in the AST, replacing or removing it based on the predicate.
|
|
161
162
|
"""
|
|
162
|
-
def __init__(self, findMe: Callable[[ast.AST], bool], nodeReplacementBuilder: Callable[[ast.AST],
|
|
163
|
+
def __init__(self, findMe: Callable[[ast.AST], bool], nodeReplacementBuilder: Callable[[ast.AST], ast.AST | None]) -> None:
|
|
163
164
|
self.findMe = findMe
|
|
164
165
|
self.nodeReplacementBuilder = nodeReplacementBuilder
|
|
165
166
|
|
|
@@ -181,10 +182,10 @@ def thisIsAnyNumbaJitDecorator(Ima: ast.AST) -> bool:
|
|
|
181
182
|
# Domain-based
|
|
182
183
|
class UniversalImportTracker:
|
|
183
184
|
def __init__(self) -> None:
|
|
184
|
-
self.dictionaryImportFrom:
|
|
185
|
+
self.dictionaryImportFrom: dict[str, set] = collections.defaultdict(set)
|
|
185
186
|
self.setImport = set()
|
|
186
187
|
|
|
187
|
-
def addAst(self, astImport_:
|
|
188
|
+
def addAst(self, astImport_: ast.Import | ast.ImportFrom) -> None:
|
|
188
189
|
if isinstance(astImport_, ast.Import):
|
|
189
190
|
for alias in astImport_.names:
|
|
190
191
|
self.setImport.add(alias.name)
|
|
@@ -195,10 +196,10 @@ class UniversalImportTracker:
|
|
|
195
196
|
def addImportStr(self, module: str) -> None:
|
|
196
197
|
self.setImport.add(module)
|
|
197
198
|
|
|
198
|
-
def addImportFromStr(self, module: str, name: str, asname:
|
|
199
|
+
def addImportFromStr(self, module: str, name: str, asname: str | None = None) -> None:
|
|
199
200
|
self.dictionaryImportFrom[module].add((name, asname))
|
|
200
201
|
|
|
201
|
-
def makeListAst(self) ->
|
|
202
|
+
def makeListAst(self) -> list[ast.ImportFrom | ast.Import]:
|
|
202
203
|
listAstImportFrom = []
|
|
203
204
|
for module, setOfNameTuples in sorted(self.dictionaryImportFrom.items()):
|
|
204
205
|
listAliases = []
|
|
@@ -253,11 +254,11 @@ class RecursiveInliner(ast.NodeTransformer):
|
|
|
253
254
|
dictionaryFunctions, its statements are expanded in place, effectively inlining
|
|
254
255
|
the called function's statements into the surrounding context.
|
|
255
256
|
"""
|
|
256
|
-
def __init__(self, dictionaryFunctions:
|
|
257
|
+
def __init__(self, dictionaryFunctions: dict[str, ast.FunctionDef]):
|
|
257
258
|
self.dictionaryFunctions = dictionaryFunctions
|
|
258
|
-
self.callablesCompleted:
|
|
259
|
+
self.callablesCompleted: set[str] = set()
|
|
259
260
|
|
|
260
|
-
def inlineFunctionBody(self, callableTargetName: str) ->
|
|
261
|
+
def inlineFunctionBody(self, callableTargetName: str) -> ast.FunctionDef | None:
|
|
261
262
|
if (callableTargetName in self.callablesCompleted):
|
|
262
263
|
return None
|
|
263
264
|
|
|
@@ -280,7 +281,7 @@ class RecursiveInliner(ast.NodeTransformer):
|
|
|
280
281
|
return ast.Constant(value=None)
|
|
281
282
|
return callNodeVisited
|
|
282
283
|
|
|
283
|
-
def visit_Expr(self, node: ast.Expr) ->
|
|
284
|
+
def visit_Expr(self, node: ast.Expr) -> ast.AST | list[ast.AST]:
|
|
284
285
|
if (isinstance(node.value, ast.Call)):
|
|
285
286
|
if (isinstance(node.value.func, ast.Name) and node.value.func.id in self.dictionaryFunctions):
|
|
286
287
|
inlineDefinition = self.inlineFunctionBody(node.value.func.id)
|
|
@@ -313,12 +314,12 @@ class UnpackArrays(ast.NodeTransformer):
|
|
|
313
314
|
3. Replaces original array access with the local variable
|
|
314
315
|
"""
|
|
315
316
|
|
|
316
|
-
def __init__(self, enumIndexClass:
|
|
317
|
+
def __init__(self, enumIndexClass: type[EnumIndices], arrayName: str) -> None:
|
|
317
318
|
self.enumIndexClass = enumIndexClass
|
|
318
319
|
self.arrayName = arrayName
|
|
319
|
-
self.substitutions:
|
|
320
|
+
self.substitutions: dict[str, Any] = {}
|
|
320
321
|
|
|
321
|
-
def extract_member_name(self, node: ast.AST) ->
|
|
322
|
+
def extract_member_name(self, node: ast.AST) -> str | None:
|
|
322
323
|
"""Recursively extract enum member name from any node in the AST."""
|
|
323
324
|
if isinstance(node, ast.Attribute) and node.attr == 'value':
|
|
324
325
|
innerAttribute = node.value
|
|
@@ -337,7 +338,7 @@ class UnpackArrays(ast.NodeTransformer):
|
|
|
337
338
|
return ast.Name(id=member_name, ctx=node.ctx)
|
|
338
339
|
elif isinstance(node, ast.Tuple):
|
|
339
340
|
# Handle tuple slices by transforming each element
|
|
340
|
-
return ast.Tuple(elts=cast(
|
|
341
|
+
return ast.Tuple(elts=cast(list[ast.expr], [self.transform_slice_element(elt) for elt in node.elts]), ctx=node.ctx)
|
|
341
342
|
elif isinstance(node, ast.Attribute):
|
|
342
343
|
member_name = self.extract_member_name(node)
|
|
343
344
|
if member_name:
|