mapFolding 0.8.0__py3-none-any.whl → 0.8.2__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 +33 -4
- mapFolding/basecamp.py +16 -2
- mapFolding/beDRY.py +40 -32
- mapFolding/filesystem.py +124 -90
- mapFolding/noHomeYet.py +12 -0
- mapFolding/oeis.py +18 -3
- mapFolding/reference/__init__.py +38 -0
- mapFolding/reference/flattened.py +66 -47
- mapFolding/reference/hunterNumba.py +28 -4
- mapFolding/reference/irvineJavaPort.py +13 -1
- mapFolding/reference/{jax.py → jaxCount.py} +46 -27
- mapFolding/reference/lunnanNumpy.py +19 -5
- mapFolding/reference/lunnanWhile.py +19 -7
- mapFolding/reference/rotatedEntryPoint.py +20 -3
- mapFolding/reference/total_countPlus1vsPlusN.py +226 -203
- mapFolding/someAssemblyRequired/__init__.py +29 -0
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +32 -14
- mapFolding/someAssemblyRequired/ingredientsNumba.py +22 -1
- mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +193 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +3 -4
- mapFolding/someAssemblyRequired/transformDataStructures.py +168 -0
- mapFolding/someAssemblyRequired/transformationTools.py +233 -225
- mapFolding/theDao.py +19 -5
- mapFolding/theSSOT.py +89 -122
- mapfolding-0.8.2.dist-info/METADATA +187 -0
- mapfolding-0.8.2.dist-info/RECORD +39 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info}/WHEEL +1 -1
- tests/conftest.py +43 -33
- tests/test_computations.py +7 -7
- tests/test_other.py +2 -2
- mapFolding/reference/lunnan.py +0 -153
- mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -350
- mapFolding/someAssemblyRequired/synthesizeDataConverters.py +0 -117
- mapFolding/syntheticModules/numbaCountHistoricalExample.py +0 -158
- mapFolding/syntheticModules/numba_doTheNeedfulHistoricalExample.py +0 -13
- mapfolding-0.8.0.dist-info/METADATA +0 -157
- mapfolding-0.8.0.dist-info/RECORD +0 -41
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info/licenses}/LICENSE +0 -0
- {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info}/top_level.txt +0 -0
tests/conftest.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
from importlib import import_module as importlib_import_module
|
|
1
2
|
from collections.abc import Callable, Generator, Sequence
|
|
2
|
-
from
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
from mapFolding.theSSOT import ComputationState, The, getPackageDispatcher
|
|
3
7
|
from mapFolding.beDRY import getLeavesTotal, validateListDimensions, makeDataContainer
|
|
4
8
|
from mapFolding.oeis import oeisIDsImplemented, settingsOEIS
|
|
5
|
-
from
|
|
9
|
+
from mapFolding.someAssemblyRequired import RecipeSynthesizeFlow
|
|
10
|
+
from mapFolding.someAssemblyRequired.synthesizeNumbaFlow import makeNumbaFlow
|
|
11
|
+
from pathlib import Path, PurePosixPath
|
|
6
12
|
from typing import Any, ContextManager
|
|
7
13
|
import importlib.util
|
|
8
14
|
import pytest
|
|
@@ -13,7 +19,6 @@ import uuid
|
|
|
13
19
|
|
|
14
20
|
# SSOT for test data paths and filenames
|
|
15
21
|
pathDataSamples = Path("tests/dataSamples")
|
|
16
|
-
# NOTE `tmp` is not a diminutive form of temporary: it signals a technical term. And "temp" is strongly disfavored.
|
|
17
22
|
pathTmpRoot: Path = pathDataSamples / "tmp"
|
|
18
23
|
|
|
19
24
|
# The registrar maintains the register of temp files
|
|
@@ -142,7 +147,7 @@ def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.As
|
|
|
142
147
|
def mockFoldingFunction() -> Callable[..., Callable[..., None]]:
|
|
143
148
|
"""Creates a mock function that simulates _countFolds behavior."""
|
|
144
149
|
def make_mock(foldsValue: int, listDimensions: list[int]) -> Callable[..., None]:
|
|
145
|
-
mock_array = makeDataContainer(2)
|
|
150
|
+
mock_array = makeDataContainer(2, numpy.int32)
|
|
146
151
|
mock_array[0] = foldsValue
|
|
147
152
|
mapShape = validateListDimensions(listDimensions)
|
|
148
153
|
mock_array[-1] = getLeavesTotal(mapShape)
|
|
@@ -192,6 +197,11 @@ def useThisDispatcher() -> Generator[Callable[..., None], Any, None]:
|
|
|
192
197
|
yield patchDispatcher
|
|
193
198
|
basecamp.getPackageDispatcher = dispatcherOriginal
|
|
194
199
|
|
|
200
|
+
def getAlgorithmDispatcher() -> Callable[[ComputationState], ComputationState]:
|
|
201
|
+
moduleImported: ModuleType = importlib_import_module(The.logicalPathModuleSourceAlgorithm)
|
|
202
|
+
dispatcherCallable = getattr(moduleImported, The.dispatcherCallable)
|
|
203
|
+
return dispatcherCallable
|
|
204
|
+
|
|
195
205
|
@pytest.fixture
|
|
196
206
|
def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Generator[None, None, None]:
|
|
197
207
|
"""Temporarily patches getDispatcherCallable to return the algorithm dispatcher."""
|
|
@@ -199,35 +209,35 @@ def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Gener
|
|
|
199
209
|
yield
|
|
200
210
|
|
|
201
211
|
@pytest.fixture
|
|
202
|
-
def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any]) -> Callable[..., Any]:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any], pathTmpTesting: Path) -> Callable[..., Any]:
|
|
213
|
+
"""Generate synthetic Numba-optimized dispatcher module and patch the dispatcher"""
|
|
214
|
+
# Configure synthesis flow to use test directory
|
|
215
|
+
recipeFlow = RecipeSynthesizeFlow(
|
|
216
|
+
pathPackage=PurePosixPath(pathTmpTesting.absolute()),
|
|
217
|
+
Z0Z_flowLogicalPathRoot=None,
|
|
218
|
+
moduleDispatcher="test_dispatcher",
|
|
219
|
+
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
220
|
+
# dispatcherCallable="dispatcherSynthetic",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Generate optimized module in test directory
|
|
224
|
+
makeNumbaFlow(recipeFlow)
|
|
225
|
+
|
|
226
|
+
# Import synthesized dispatcher
|
|
227
|
+
importlibSpecificationDispatcher = importlib.util.spec_from_file_location(
|
|
228
|
+
recipeFlow.moduleDispatcher,
|
|
229
|
+
Path(recipeFlow.pathFilenameDispatcher),
|
|
230
|
+
)
|
|
231
|
+
if importlibSpecificationDispatcher is None or importlibSpecificationDispatcher.loader is None:
|
|
232
|
+
raise ImportError("Failed to load synthetic dispatcher module")
|
|
233
|
+
|
|
234
|
+
moduleSpecificationDispatcher = importlib.util.module_from_spec(importlibSpecificationDispatcher)
|
|
235
|
+
importlibSpecificationDispatcher.loader.exec_module(moduleSpecificationDispatcher)
|
|
236
|
+
callableDispatcherSynthetic = getattr(moduleSpecificationDispatcher, recipeFlow.dispatcherCallable)
|
|
237
|
+
|
|
238
|
+
# Patch dispatcher and return callable
|
|
239
|
+
useThisDispatcher(callableDispatcherSynthetic)
|
|
240
|
+
return callableDispatcherSynthetic
|
|
231
241
|
|
|
232
242
|
def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
233
243
|
"""Format assertion message for any test comparison."""
|
tests/test_computations.py
CHANGED
|
@@ -23,6 +23,12 @@ def test_aOFn_calculate_value(oeisID: str) -> None:
|
|
|
23
23
|
for n in settingsOEIS[oeisID]['valuesTestValidation']:
|
|
24
24
|
standardizedEqualToCallableReturn(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
|
|
25
25
|
|
|
26
|
+
def test_syntheticParallel(syntheticDispatcherFixture: None, listDimensionsTestParallelization: list[int]):
|
|
27
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestParallelization)), countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
28
|
+
|
|
29
|
+
def test_syntheticSequential(syntheticDispatcherFixture: None, listDimensionsTestCountFolds: list[int]) -> None:
|
|
30
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestCountFolds)), countFolds, listDimensionsTestCountFolds)
|
|
31
|
+
|
|
26
32
|
# @pytest.mark.parametrize('pathFilenameTmpTesting', ['.py'], indirect=True)
|
|
27
33
|
# def test_writeJobNumba(listDimensionsTestCountFolds: list[int], pathFilenameTmpTesting: Path) -> None:
|
|
28
34
|
# from mapFolding.syntheticModules import numbaCount
|
|
@@ -44,10 +50,4 @@ def test_aOFn_calculate_value(oeisID: str) -> None:
|
|
|
44
50
|
|
|
45
51
|
# pathFilenameFoldsTotal = getPathFilenameFoldsTotal(listDimensionsTestCountFolds)
|
|
46
52
|
# registrarRecordsTmpObject(pathFilenameFoldsTotal)
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
# def test_syntheticParallel(syntheticDispatcherFixture: None, listDimensionsTestParallelization: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
50
|
-
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestParallelization)], countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
51
|
-
|
|
52
|
-
# def test_syntheticSequential(syntheticDispatcherFixture: None, listDimensionsTestCountFolds: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
53
|
-
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestCountFolds)], countFolds, listDimensionsTestCountFolds)
|
|
53
|
+
# standardizedEqualToCallableReturn(str(getFoldsTotalKnown(tuple(listDimensionsTestCountFolds))), pathFilenameFoldsTotal.read_text().strip)
|
tests/test_other.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from mapFolding.beDRY import getLeavesTotal, setCPUlimit, validateListDimensions
|
|
3
|
+
from mapFolding.theSSOT import The
|
|
3
4
|
from tests.conftest import standardizedEqualToCallableReturn
|
|
4
5
|
from typing import Any, Literal
|
|
5
6
|
from Z0Z_tools import intInnit
|
|
@@ -78,6 +79,5 @@ def testOopsieKwargsie(nameOfTest: str, callablePytest: Callable[[], None]) -> N
|
|
|
78
79
|
(1, 1),
|
|
79
80
|
])
|
|
80
81
|
def test_setCPUlimit(CPUlimit: None | float | bool | Literal[4] | Literal[-2] | Literal[0] | Literal[1], expectedLimit: Any | int) -> None:
|
|
81
|
-
|
|
82
|
-
if concurrencyPackage == 'numba':
|
|
82
|
+
if The.concurrencyPackage == 'numba':
|
|
83
83
|
standardizedEqualToCallableReturn(expectedLimit, setCPUlimit, CPUlimit)
|
mapFolding/reference/lunnan.py
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
An unnecessarily literal translation of the original Atlas Autocode code by W. F. Lunnon to Python.
|
|
3
|
-
W. F. Lunnon, Multi-dimensional map-folding, The Computer Journal, Volume 14, Issue 1, 1971, Pages 75-80, https://doi.org/10.1093/comjnl/14.1.75
|
|
4
|
-
"""# NOTE not functional yet
|
|
5
|
-
def foldings(p, job=None):
|
|
6
|
-
"""An unnecessarily literal translation of the original Atlas Autocode code."""
|
|
7
|
-
p = list(p)
|
|
8
|
-
p.append(None) # NOTE mimics Atlas `array` type
|
|
9
|
-
p.insert(0, None) # NOTE mimics Atlas `array` type
|
|
10
|
-
|
|
11
|
-
if job is None:
|
|
12
|
-
global G
|
|
13
|
-
G = 0
|
|
14
|
-
def job(A, B):
|
|
15
|
-
global G
|
|
16
|
-
G = G + 1
|
|
17
|
-
return foldings(p, job)
|
|
18
|
-
# perform job (A, B) on each folding of a p[1] x ... x p[d] map,
|
|
19
|
-
# where A and B are the above and below vectors. p[d + 1] < 0 terminates p;
|
|
20
|
-
|
|
21
|
-
d: int
|
|
22
|
-
n: int
|
|
23
|
-
j: int
|
|
24
|
-
i: int
|
|
25
|
-
m: int
|
|
26
|
-
l: int
|
|
27
|
-
g: int
|
|
28
|
-
gg: int
|
|
29
|
-
dd: int
|
|
30
|
-
|
|
31
|
-
n = 1
|
|
32
|
-
i, d = 0, 0
|
|
33
|
-
|
|
34
|
-
while (i := i + 1) and p[i] is not None:
|
|
35
|
-
d = i
|
|
36
|
-
n = n * p[i]
|
|
37
|
-
|
|
38
|
-
# d dimensions and n leaves;
|
|
39
|
-
|
|
40
|
-
# A: list[int] = [None] * (n + 1) # type: ignore
|
|
41
|
-
# B: list[int] = [None] * (n + 1) # type: ignore
|
|
42
|
-
# count: list[int] = [None] * (n + 1) # type: ignore
|
|
43
|
-
# gapter: list[int] = [None] * (n + 1) # type: ignore
|
|
44
|
-
# gap: list[int] = [None] * (n * n + 1) # type: ignore
|
|
45
|
-
A: list[int] = [0] * (n + 1) # type: ignore
|
|
46
|
-
B: list[int] = [0] * (n + 1) # type: ignore
|
|
47
|
-
count: list[int] = [0] * (n + 1) # type: ignore
|
|
48
|
-
gapter: list[int] = [0] * (n + 1) # type: ignore
|
|
49
|
-
gap: list[int] = [0] * (n * n + 1) # type: ignore
|
|
50
|
-
|
|
51
|
-
# B[m] is the leaf below leaf m in the current folding,
|
|
52
|
-
# A[m] the leaf above. count[m] is the no. of sections in which
|
|
53
|
-
# there is a gap for the new leaf l below leaf m,
|
|
54
|
-
# gap[gapter[l - 1] + j] is the j-th (possible or actual) gap for leaf l,
|
|
55
|
-
# and later gap[gapter[l]] is the gap where leaf l is currently inserted;
|
|
56
|
-
|
|
57
|
-
P: list[int] = [0] * (d + 1) # type: ignore
|
|
58
|
-
C: list[list[int]] = [[0] * (n + 1) for dimension1 in range(d + 1)] # type: ignore
|
|
59
|
-
# D: list[list[list[int]]] = [[[None] * (n + 1) for dimension2 in range(n + 1)] for dimension1 in range(d + 1)] # type: ignore
|
|
60
|
-
D: list[list[list[int]]] = [[[0] * (n + 1) for dimension2 in range(n + 1)] for dimension1 in range(d + 1)]
|
|
61
|
-
|
|
62
|
-
P[0] = 1
|
|
63
|
-
for i in range(1, d + 1):
|
|
64
|
-
P[i] = P[i - 1] * p[i]
|
|
65
|
-
|
|
66
|
-
for i in range(1, d + 1):
|
|
67
|
-
for m in range(1, n + 1):
|
|
68
|
-
C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i] + 1
|
|
69
|
-
|
|
70
|
-
for i in range(1, d + 1):
|
|
71
|
-
for l in range(1, n + 1):
|
|
72
|
-
for m in range(1, l + 1):
|
|
73
|
-
D[i][l][m] = (0 if m == 0
|
|
74
|
-
else
|
|
75
|
-
((m if C[i][m] == 1
|
|
76
|
-
else m - P[i - 1])
|
|
77
|
-
if C[i][l] - C[i][m] == (C[i][l] - C[i][m]) // 2 * 2
|
|
78
|
-
else
|
|
79
|
-
(m if C[i][m] == p[i] or m + P[i - 1] > l
|
|
80
|
-
else m + P[i - 1])))
|
|
81
|
-
# P[i] = p[1] x ... x p[i], C[i][m] = i-th co-ordinate of leaf m,
|
|
82
|
-
# D[i][l][m] = leaf connected to m in section i when inserting l;
|
|
83
|
-
|
|
84
|
-
for m in range(n + 1):
|
|
85
|
-
count[m] = 0
|
|
86
|
-
|
|
87
|
-
A[0], B[0], g, l = 0, 0, 0, 0
|
|
88
|
-
|
|
89
|
-
state = 'entry'
|
|
90
|
-
while True:
|
|
91
|
-
if state == 'entry':
|
|
92
|
-
gapter[l] = g
|
|
93
|
-
l = l + 1
|
|
94
|
-
if l <= n:
|
|
95
|
-
state = 'down'
|
|
96
|
-
continue
|
|
97
|
-
else:
|
|
98
|
-
job(A, B)
|
|
99
|
-
state = 'up'
|
|
100
|
-
continue
|
|
101
|
-
|
|
102
|
-
elif state == 'down':
|
|
103
|
-
dd = 0
|
|
104
|
-
gg = gapter[l - 1]
|
|
105
|
-
g = gg
|
|
106
|
-
for i in range(1, d + 1):
|
|
107
|
-
if D[i][l][l] == l:
|
|
108
|
-
dd = dd + 1
|
|
109
|
-
else:
|
|
110
|
-
m = D[i][l][l]
|
|
111
|
-
while m != l:
|
|
112
|
-
gap[gg] = m
|
|
113
|
-
if count[m] == 0:
|
|
114
|
-
gg = gg + 1
|
|
115
|
-
count[m] = count[m] + 1
|
|
116
|
-
m = D[i][l][B[m]]
|
|
117
|
-
|
|
118
|
-
if dd == d:
|
|
119
|
-
for m in range(l):
|
|
120
|
-
gap[gg] = m
|
|
121
|
-
gg = gg + 1
|
|
122
|
-
|
|
123
|
-
for j in range(g, gg):
|
|
124
|
-
gap[g] = gap[j]
|
|
125
|
-
if count[gap[j]] == d - dd:
|
|
126
|
-
g = g + 1
|
|
127
|
-
count[gap[j]] = 0
|
|
128
|
-
state = 'along'
|
|
129
|
-
continue
|
|
130
|
-
|
|
131
|
-
elif state == 'along':
|
|
132
|
-
if g == gapter[l - 1]:
|
|
133
|
-
state = 'up'
|
|
134
|
-
continue
|
|
135
|
-
g = g - 1
|
|
136
|
-
A[l] = gap[g]
|
|
137
|
-
B[l] = B[A[l]]
|
|
138
|
-
B[A[l]] = l
|
|
139
|
-
A[B[l]] = l
|
|
140
|
-
state = 'entry'
|
|
141
|
-
continue
|
|
142
|
-
|
|
143
|
-
elif state == 'up':
|
|
144
|
-
l = l - 1
|
|
145
|
-
B[A[l]] = B[l]
|
|
146
|
-
A[B[l]] = A[l]
|
|
147
|
-
if l > 0:
|
|
148
|
-
state = 'along'
|
|
149
|
-
continue
|
|
150
|
-
else:
|
|
151
|
-
break
|
|
152
|
-
|
|
153
|
-
return G #if job.__closure__ else None
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
from autoflake import fix_code as autoflake_fix_code
|
|
2
|
-
from mapFolding.filesystem import writeStringToHere
|
|
3
|
-
from mapFolding.someAssemblyRequired import (
|
|
4
|
-
ast_Identifier,
|
|
5
|
-
extractFunctionDef,
|
|
6
|
-
ifThis,
|
|
7
|
-
IngredientsFunction,
|
|
8
|
-
IngredientsModule,
|
|
9
|
-
LedgerOfImports,
|
|
10
|
-
Make,
|
|
11
|
-
makeDictionaryReplacementStatements,
|
|
12
|
-
NodeCollector,
|
|
13
|
-
NodeReplacer,
|
|
14
|
-
RecipeSynthesizeFlow,
|
|
15
|
-
strDotStrCuzPyStoopid,
|
|
16
|
-
Then,
|
|
17
|
-
)
|
|
18
|
-
from mapFolding.someAssemblyRequired.ingredientsNumba import decorateCallableWithNumba
|
|
19
|
-
from mapFolding.someAssemblyRequired.synthesizeDataConverters import shatter_dataclassesDOTdataclass
|
|
20
|
-
from mapFolding.theSSOT import raiseIfNoneGitHubIssueNumber3
|
|
21
|
-
from pathlib import Path
|
|
22
|
-
import ast
|
|
23
|
-
|
|
24
|
-
# Would `LibCST` be better than `ast` in some cases? https://github.com/hunterhogan/mapFolding/issues/7
|
|
25
|
-
|
|
26
|
-
def Z0Z_alphaTest_putModuleOnDisk(ingredients: IngredientsModule, recipeFlow: RecipeSynthesizeFlow):
|
|
27
|
-
# Physical namespace
|
|
28
|
-
filenameStem: str = recipeFlow.moduleDispatcher
|
|
29
|
-
fileExtension: str = recipeFlow.fileExtension
|
|
30
|
-
pathPackage: Path = Path(recipeFlow.pathPackage)
|
|
31
|
-
|
|
32
|
-
# Physical and logical namespace
|
|
33
|
-
packageName: ast_Identifier | None = recipeFlow.packageName # module name of the package, if any
|
|
34
|
-
logicalPathINFIX: ast_Identifier | strDotStrCuzPyStoopid | None = recipeFlow.Z0Z_flowLogicalPathRoot
|
|
35
|
-
|
|
36
|
-
def _getLogicalPathParent() -> str | None:
|
|
37
|
-
listModules: list[ast_Identifier] = []
|
|
38
|
-
if packageName:
|
|
39
|
-
listModules.append(packageName)
|
|
40
|
-
if logicalPathINFIX:
|
|
41
|
-
listModules.append(logicalPathINFIX)
|
|
42
|
-
if listModules:
|
|
43
|
-
return '.'.join(listModules)
|
|
44
|
-
return None
|
|
45
|
-
|
|
46
|
-
def _getLogicalPathAbsolute() -> str:
|
|
47
|
-
listModules: list[ast_Identifier] = []
|
|
48
|
-
logicalPathParent: str | None = _getLogicalPathParent()
|
|
49
|
-
if logicalPathParent:
|
|
50
|
-
listModules.append(logicalPathParent)
|
|
51
|
-
listModules.append(filenameStem)
|
|
52
|
-
return '.'.join(listModules)
|
|
53
|
-
|
|
54
|
-
def getPathFilename():
|
|
55
|
-
pathRoot: Path = pathPackage
|
|
56
|
-
filename: str = filenameStem + fileExtension
|
|
57
|
-
if logicalPathINFIX:
|
|
58
|
-
whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
|
|
59
|
-
pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
|
|
60
|
-
return pathRoot.joinpath(filename)
|
|
61
|
-
|
|
62
|
-
def absoluteImport() -> ast.Import:
|
|
63
|
-
return Make.astImport(_getLogicalPathAbsolute())
|
|
64
|
-
|
|
65
|
-
def absoluteImportFrom() -> ast.ImportFrom:
|
|
66
|
-
""" `from . import theModule` """
|
|
67
|
-
logicalPathParent: str = _getLogicalPathParent() or '.'
|
|
68
|
-
return Make.astImportFrom(logicalPathParent, [Make.astAlias(filenameStem)])
|
|
69
|
-
|
|
70
|
-
def writeModule() -> None:
|
|
71
|
-
astModule = ingredients.export()
|
|
72
|
-
ast.fix_missing_locations(astModule)
|
|
73
|
-
pythonSource: str = ast.unparse(astModule)
|
|
74
|
-
if not pythonSource: raise raiseIfNoneGitHubIssueNumber3
|
|
75
|
-
autoflake_additional_imports: list[str] = ingredients.imports.exportListModuleNames()
|
|
76
|
-
if packageName:
|
|
77
|
-
autoflake_additional_imports.append(packageName)
|
|
78
|
-
pythonSource = autoflake_fix_code(pythonSource, autoflake_additional_imports, expand_star_imports=False, remove_all_unused_imports=False, remove_duplicate_keys = False, remove_unused_variables = False,)
|
|
79
|
-
pathFilename = getPathFilename()
|
|
80
|
-
writeStringToHere(pythonSource, pathFilename)
|
|
81
|
-
|
|
82
|
-
writeModule()
|
|
83
|
-
|
|
84
|
-
def inlineThisFunctionWithTheseValues(astFunctionDef: ast.FunctionDef, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> ast.FunctionDef:
|
|
85
|
-
class FunctionInliner(ast.NodeTransformer):
|
|
86
|
-
def __init__(self, dictionaryReplacementStatements: dict[str, ast.stmt | list[ast.stmt]]) -> None:
|
|
87
|
-
self.dictionaryReplacementStatements = dictionaryReplacementStatements
|
|
88
|
-
|
|
89
|
-
def generic_visit(self, node: ast.AST) -> ast.AST:
|
|
90
|
-
"""Visit all nodes and replace them if necessary."""
|
|
91
|
-
return super().generic_visit(node)
|
|
92
|
-
|
|
93
|
-
def visit_Expr(self, node: ast.Expr) -> ast.AST | list[ast.stmt]:
|
|
94
|
-
"""Visit Expr nodes and replace value if it's a function call in our dictionary."""
|
|
95
|
-
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
|
|
96
|
-
return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
|
|
97
|
-
return node
|
|
98
|
-
|
|
99
|
-
def visit_Assign(self, node: ast.Assign) -> ast.AST | list[ast.stmt]:
|
|
100
|
-
"""Visit Assign nodes and replace value if it's a function call in our dictionary."""
|
|
101
|
-
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node.value):
|
|
102
|
-
return self.dictionaryReplacementStatements[node.value.func.id] # type: ignore[attr-defined]
|
|
103
|
-
return node
|
|
104
|
-
|
|
105
|
-
def visit_Call(self, node: ast.Call) -> ast.AST | list[ast.stmt]:
|
|
106
|
-
"""Replace call nodes with their replacement statements if they're in the dictionary."""
|
|
107
|
-
if ifThis.CallDoesNotCallItselfAndNameDOTidIsIn(self.dictionaryReplacementStatements)(node):
|
|
108
|
-
replacement = self.dictionaryReplacementStatements[node.func.id] # type: ignore[attr-defined]
|
|
109
|
-
if not isinstance(replacement, list):
|
|
110
|
-
return replacement
|
|
111
|
-
return node
|
|
112
|
-
|
|
113
|
-
import copy
|
|
114
|
-
keepGoing = True
|
|
115
|
-
ImaInlineFunction = copy.deepcopy(astFunctionDef)
|
|
116
|
-
while keepGoing:
|
|
117
|
-
ImaInlineFunction = copy.deepcopy(astFunctionDef)
|
|
118
|
-
FunctionInliner(copy.deepcopy(dictionaryReplacementStatements)).visit(ImaInlineFunction)
|
|
119
|
-
if ast.unparse(ImaInlineFunction) == ast.unparse(astFunctionDef):
|
|
120
|
-
keepGoing = False
|
|
121
|
-
else:
|
|
122
|
-
astFunctionDef = copy.deepcopy(ImaInlineFunction)
|
|
123
|
-
return ImaInlineFunction
|
|
124
|
-
|
|
125
|
-
def replaceMatchingASTnodes(astTree: ast.AST, replacementMap: list[tuple[ast.AST, ast.AST]]) -> ast.AST:
|
|
126
|
-
"""Replace matching AST nodes using type-specific visitors.
|
|
127
|
-
|
|
128
|
-
Parameters:
|
|
129
|
-
astTree: The AST to transform
|
|
130
|
-
replacementMap: List of (find, replace) node pairs
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
The transformed AST
|
|
134
|
-
"""
|
|
135
|
-
class TargetedNodeReplacer(ast.NodeTransformer):
|
|
136
|
-
def __init__(self, replacementMap: list[tuple[ast.AST, ast.AST]]) -> None:
|
|
137
|
-
# Group replacements by node type for more efficient lookups
|
|
138
|
-
self.replacementByType: dict[type[ast.AST], list[tuple[ast.AST, ast.AST]]] = {}
|
|
139
|
-
for findNode, replaceNode in replacementMap:
|
|
140
|
-
nodeType = type(findNode)
|
|
141
|
-
if nodeType not in self.replacementByType:
|
|
142
|
-
self.replacementByType[nodeType] = []
|
|
143
|
-
self.replacementByType[nodeType].append((findNode, replaceNode))
|
|
144
|
-
|
|
145
|
-
def visit(self, node: ast.AST) -> ast.AST:
|
|
146
|
-
"""Check if this node should be replaced before continuing traversal."""
|
|
147
|
-
nodeType = type(node)
|
|
148
|
-
if nodeType in self.replacementByType:
|
|
149
|
-
for findNode, replaceNode in self.replacementByType[nodeType]:
|
|
150
|
-
if self.nodesMatchStructurally(node, findNode):
|
|
151
|
-
return replaceNode
|
|
152
|
-
return super().visit(node)
|
|
153
|
-
|
|
154
|
-
def nodesMatchStructurally(self, node1: ast.AST | list, node2: ast.AST | list) -> bool:
|
|
155
|
-
"""Compare two AST nodes structurally, ignoring position information."""
|
|
156
|
-
# Different types can't be equal
|
|
157
|
-
if type(node1) != type(node2):
|
|
158
|
-
return False
|
|
159
|
-
|
|
160
|
-
if isinstance(node1, ast.AST):
|
|
161
|
-
# Compare fields that matter for structural equality
|
|
162
|
-
fields = [f for f in node1._fields
|
|
163
|
-
if f not in ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'ctx')]
|
|
164
|
-
|
|
165
|
-
for field in fields:
|
|
166
|
-
smurf1 = getattr(node1, field, None)
|
|
167
|
-
smurf2 = getattr(node2, field, None)
|
|
168
|
-
|
|
169
|
-
if isinstance(smurf1, (ast.AST, list)) and isinstance(smurf2, (ast.AST, list)):
|
|
170
|
-
if not self.nodesMatchStructurally(smurf1, smurf2):
|
|
171
|
-
return False
|
|
172
|
-
elif smurf1 != smurf2:
|
|
173
|
-
return False
|
|
174
|
-
return True
|
|
175
|
-
|
|
176
|
-
elif isinstance(node1, list) and isinstance(node2, list):
|
|
177
|
-
if len(node1) != len(node2):
|
|
178
|
-
return False
|
|
179
|
-
return all(self.nodesMatchStructurally(x, y) for x, y in zip(node1, node2))
|
|
180
|
-
|
|
181
|
-
else:
|
|
182
|
-
# Direct comparison for non-AST objects (strings, numbers, etc.)
|
|
183
|
-
return node1 == node2
|
|
184
|
-
|
|
185
|
-
import copy
|
|
186
|
-
keepGoing = True
|
|
187
|
-
astResult = copy.deepcopy(astTree)
|
|
188
|
-
|
|
189
|
-
while keepGoing:
|
|
190
|
-
astBeforeChange = copy.deepcopy(astResult)
|
|
191
|
-
TargetedNodeReplacer(copy.deepcopy(replacementMap)).visit(astResult)
|
|
192
|
-
|
|
193
|
-
# Check if we've reached a fixed point (no more changes)
|
|
194
|
-
if ast.unparse(astResult) == ast.unparse(astBeforeChange):
|
|
195
|
-
keepGoing = False
|
|
196
|
-
|
|
197
|
-
return astResult
|
|
198
|
-
|
|
199
|
-
def Z0Z_main() -> None:
|
|
200
|
-
numbaFlow: RecipeSynthesizeFlow = RecipeSynthesizeFlow()
|
|
201
|
-
dictionaryReplacementStatements = makeDictionaryReplacementStatements(numbaFlow.source_astModule)
|
|
202
|
-
# TODO remove hardcoding
|
|
203
|
-
theCountingIdentifierHARDCODED = 'groupsOfFolds'
|
|
204
|
-
theCountingIdentifier = theCountingIdentifierHARDCODED
|
|
205
|
-
|
|
206
|
-
# TODO remember that `sequentialCallable` and `sourceSequentialCallable` are two different values.
|
|
207
|
-
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
208
|
-
|
|
209
|
-
# ===========================================================
|
|
210
|
-
sourcePython = numbaFlow.sourceDispatcherCallable
|
|
211
|
-
astFunctionDef = extractFunctionDef(sourcePython, numbaFlow.source_astModule)
|
|
212
|
-
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
213
|
-
ingredientsDispatcher = IngredientsFunction(astFunctionDef, LedgerOfImports(numbaFlow.source_astModule))
|
|
214
|
-
|
|
215
|
-
# sourceParallelCallable
|
|
216
|
-
(astName_dataclassesDOTdataclass, ledgerDataclassANDFragments, listAnnAssign4DataclassUnpack,
|
|
217
|
-
astTuple4AssignTargetsToFragments, listNameDataclassFragments4Parameters, list_ast_argAnnotated4ArgumentsSpecification,
|
|
218
|
-
astSubscriptPrimitiveTupleAnnotations4FunctionDef_returns, astAssignDataclassRepack, list_keyword4DataclassInitialization) = shatter_dataclassesDOTdataclass(
|
|
219
|
-
numbaFlow.logicalPathModuleDataclass, numbaFlow.sourceDataclassIdentifier, numbaFlow.sourceDataclassInstanceTaskDistribution)
|
|
220
|
-
ingredientsDispatcher.imports.update(ledgerDataclassANDFragments)
|
|
221
|
-
|
|
222
|
-
# TODO remove hardcoding
|
|
223
|
-
namespaceHARDCODED = 'concurrencyManager'
|
|
224
|
-
identifierHARDCODED = 'submit'
|
|
225
|
-
namespace = namespaceHARDCODED
|
|
226
|
-
identifier = identifierHARDCODED
|
|
227
|
-
NodeReplacer(
|
|
228
|
-
findThis = ifThis.isAssignAndValueIsCallNamespace_Identifier(namespace, identifier)
|
|
229
|
-
, doThat = Then.insertThisAbove(listAnnAssign4DataclassUnpack)
|
|
230
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
231
|
-
NodeReplacer(
|
|
232
|
-
findThis = ifThis.isCallNamespace_Identifier(namespace, identifier)
|
|
233
|
-
, doThat = Then.replaceWith(Make.astCall(Make.astAttribute(Make.astName(namespace), identifier)
|
|
234
|
-
, listArguments=[Make.astName(numbaFlow.parallelCallable)] + listNameDataclassFragments4Parameters))
|
|
235
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
236
|
-
|
|
237
|
-
CapturedAssign: list[ast.AST] = []
|
|
238
|
-
CapturedCall: list[ast.Call] = []
|
|
239
|
-
findThis = ifThis.isCall
|
|
240
|
-
doThat = [Then.appendTo(CapturedCall)]
|
|
241
|
-
capture = NodeCollector(findThis, doThat)
|
|
242
|
-
|
|
243
|
-
NodeCollector(
|
|
244
|
-
findThis = ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(numbaFlow.sourceDataclassInstance))
|
|
245
|
-
, doThat = [Then.appendTo(CapturedAssign)
|
|
246
|
-
, lambda node: capture.visit(node)]
|
|
247
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
248
|
-
|
|
249
|
-
newAssign = CapturedAssign[0]
|
|
250
|
-
NodeReplacer(
|
|
251
|
-
findThis = lambda node: ifThis.isSubscript(node) and ifThis.isAttribute(node.value) and ifThis.isCall(node.value.value)
|
|
252
|
-
, doThat = Then.replaceWith(CapturedCall[0])
|
|
253
|
-
).visit(newAssign)
|
|
254
|
-
|
|
255
|
-
NodeReplacer(
|
|
256
|
-
findThis = ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(numbaFlow.sourceDataclassInstance))
|
|
257
|
-
, doThat = Then.replaceWith(newAssign)
|
|
258
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
259
|
-
|
|
260
|
-
# sourceSequentialCallable
|
|
261
|
-
(astName_dataclassesDOTdataclass, ledgerDataclassANDFragments, listAnnAssign4DataclassUnpack,
|
|
262
|
-
astTuple4AssignTargetsToFragments, listNameDataclassFragments4Parameters, list_ast_argAnnotated4ArgumentsSpecification,
|
|
263
|
-
astSubscriptPrimitiveTupleAnnotations4FunctionDef_returns, astAssignDataclassRepack, list_keyword4DataclassInitialization) = shatter_dataclassesDOTdataclass(
|
|
264
|
-
numbaFlow.logicalPathModuleDataclass, numbaFlow.sourceDataclassIdentifier, numbaFlow.sourceDataclassInstance)
|
|
265
|
-
ingredientsDispatcher.imports.update(ledgerDataclassANDFragments)
|
|
266
|
-
|
|
267
|
-
NodeReplacer(
|
|
268
|
-
findThis = ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.sourceSequentialCallable)
|
|
269
|
-
, doThat = Then.insertThisAbove(listAnnAssign4DataclassUnpack)
|
|
270
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
271
|
-
NodeReplacer(
|
|
272
|
-
findThis = ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.sourceSequentialCallable)
|
|
273
|
-
# findThis = ifThis.isReturn
|
|
274
|
-
, doThat = Then.insertThisBelow([astAssignDataclassRepack])
|
|
275
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
276
|
-
# TODO reconsider: This calls a function, but I don't inspect the function for its parameters or return.
|
|
277
|
-
NodeReplacer(
|
|
278
|
-
findThis = ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.sourceSequentialCallable)
|
|
279
|
-
, doThat = Then.replaceWith(Make.astAssign(listTargets=[astTuple4AssignTargetsToFragments], value=Make.astCall(Make.astName(numbaFlow.sequentialCallable), listNameDataclassFragments4Parameters)))
|
|
280
|
-
).visit(ingredientsDispatcher.astFunctionDef)
|
|
281
|
-
|
|
282
|
-
# ===========================================================
|
|
283
|
-
sourcePython = numbaFlow.sourceInitializeCallable
|
|
284
|
-
astFunctionDef = extractFunctionDef(sourcePython, numbaFlow.source_astModule)
|
|
285
|
-
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
286
|
-
astFunctionDef = inlineThisFunctionWithTheseValues(astFunctionDef, dictionaryReplacementStatements)
|
|
287
|
-
ingredientsInitialize = IngredientsFunction(astFunctionDef, LedgerOfImports(numbaFlow.source_astModule))
|
|
288
|
-
|
|
289
|
-
# ===========================================================
|
|
290
|
-
sourcePython = numbaFlow.sourceParallelCallable
|
|
291
|
-
astFunctionDef = extractFunctionDef(sourcePython, numbaFlow.source_astModule)
|
|
292
|
-
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
293
|
-
astFunctionDef = inlineThisFunctionWithTheseValues(astFunctionDef, dictionaryReplacementStatements)
|
|
294
|
-
ingredientsParallel = IngredientsFunction(astFunctionDef, LedgerOfImports(numbaFlow.source_astModule))
|
|
295
|
-
ingredientsParallel.astFunctionDef.name = numbaFlow.parallelCallable
|
|
296
|
-
ingredientsParallel.astFunctionDef.args = Make.astArgumentsSpecification(args=list_ast_argAnnotated4ArgumentsSpecification)
|
|
297
|
-
NodeReplacer(
|
|
298
|
-
findThis = ifThis.isReturn
|
|
299
|
-
, doThat = Then.replaceWith(Make.astReturn(astTuple4AssignTargetsToFragments))
|
|
300
|
-
).visit(ingredientsParallel.astFunctionDef)
|
|
301
|
-
NodeReplacer(
|
|
302
|
-
findThis = ifThis.isReturn
|
|
303
|
-
# , doThat = Then.replaceWith(Make.astReturn(astTuple4AssignTargetsToFragments))
|
|
304
|
-
, doThat = Then.replaceWith(Make.astReturn(Make.astName(theCountingIdentifier)))
|
|
305
|
-
).visit(ingredientsParallel.astFunctionDef)
|
|
306
|
-
theCountingIdentifierAnnotation = next(
|
|
307
|
-
ast_arg.annotation for ast_arg in list_ast_argAnnotated4ArgumentsSpecification if ast_arg.arg == theCountingIdentifier)
|
|
308
|
-
ingredientsParallel.astFunctionDef.returns = theCountingIdentifierAnnotation
|
|
309
|
-
# ingredientsParallel.astFunctionDef.returns = astSubscriptPrimitiveTupleAnnotations4FunctionDef_returns
|
|
310
|
-
replacementMap = [(statement.value, statement.target) for statement in listAnnAssign4DataclassUnpack]
|
|
311
|
-
ingredientsParallel.astFunctionDef = replaceMatchingASTnodes(
|
|
312
|
-
ingredientsParallel.astFunctionDef, replacementMap) # type: ignore
|
|
313
|
-
# TODO a tool to automatically remove unused variables from the ArgumentsSpecification (return, and returns) _might_ be nice.
|
|
314
|
-
# But, I would need to update the calling function, too.
|
|
315
|
-
ingredientsParallel = decorateCallableWithNumba(ingredientsParallel) # parametersNumbaParallelDEFAULT
|
|
316
|
-
|
|
317
|
-
# ===========================================================
|
|
318
|
-
sourcePython = numbaFlow.sourceSequentialCallable
|
|
319
|
-
astFunctionDef = extractFunctionDef(sourcePython, numbaFlow.source_astModule)
|
|
320
|
-
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
321
|
-
astFunctionDef = inlineThisFunctionWithTheseValues(astFunctionDef, dictionaryReplacementStatements)
|
|
322
|
-
ingredientsSequential = IngredientsFunction(astFunctionDef, LedgerOfImports(numbaFlow.source_astModule))
|
|
323
|
-
ingredientsSequential.astFunctionDef.name = numbaFlow.sequentialCallable
|
|
324
|
-
ingredientsSequential.astFunctionDef.args = Make.astArgumentsSpecification(args=list_ast_argAnnotated4ArgumentsSpecification)
|
|
325
|
-
NodeReplacer(
|
|
326
|
-
findThis = ifThis.isReturn
|
|
327
|
-
, doThat = Then.replaceWith(Make.astReturn(astTuple4AssignTargetsToFragments))
|
|
328
|
-
).visit(ingredientsSequential.astFunctionDef)
|
|
329
|
-
NodeReplacer(
|
|
330
|
-
findThis = ifThis.isReturn
|
|
331
|
-
, doThat = Then.replaceWith(Make.astReturn(astTuple4AssignTargetsToFragments))
|
|
332
|
-
).visit(ingredientsSequential.astFunctionDef)
|
|
333
|
-
ingredientsSequential.astFunctionDef.returns = astSubscriptPrimitiveTupleAnnotations4FunctionDef_returns
|
|
334
|
-
replacementMap = [(statement.value, statement.target) for statement in listAnnAssign4DataclassUnpack]
|
|
335
|
-
ingredientsSequential.astFunctionDef = replaceMatchingASTnodes(
|
|
336
|
-
ingredientsSequential.astFunctionDef, replacementMap) # type: ignore
|
|
337
|
-
# TODO a tool to automatically remove unused variables from the ArgumentsSpecification (return, and returns) _might_ be nice.
|
|
338
|
-
# But, I would need to update the calling function, too.
|
|
339
|
-
ingredientsSequential = decorateCallableWithNumba(ingredientsSequential)
|
|
340
|
-
|
|
341
|
-
ingredientsModuleNumbaUnified = IngredientsModule(
|
|
342
|
-
ingredientsFunction=[ingredientsInitialize,
|
|
343
|
-
ingredientsParallel,
|
|
344
|
-
ingredientsSequential,
|
|
345
|
-
ingredientsDispatcher], imports=LedgerOfImports(numbaFlow.source_astModule))
|
|
346
|
-
|
|
347
|
-
Z0Z_alphaTest_putModuleOnDisk(ingredientsModuleNumbaUnified, numbaFlow)
|
|
348
|
-
|
|
349
|
-
if __name__ == '__main__':
|
|
350
|
-
Z0Z_main()
|