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.
Files changed (40) hide show
  1. mapFolding/__init__.py +33 -4
  2. mapFolding/basecamp.py +16 -2
  3. mapFolding/beDRY.py +40 -32
  4. mapFolding/filesystem.py +124 -90
  5. mapFolding/noHomeYet.py +12 -0
  6. mapFolding/oeis.py +18 -3
  7. mapFolding/reference/__init__.py +38 -0
  8. mapFolding/reference/flattened.py +66 -47
  9. mapFolding/reference/hunterNumba.py +28 -4
  10. mapFolding/reference/irvineJavaPort.py +13 -1
  11. mapFolding/reference/{jax.py → jaxCount.py} +46 -27
  12. mapFolding/reference/lunnanNumpy.py +19 -5
  13. mapFolding/reference/lunnanWhile.py +19 -7
  14. mapFolding/reference/rotatedEntryPoint.py +20 -3
  15. mapFolding/reference/total_countPlus1vsPlusN.py +226 -203
  16. mapFolding/someAssemblyRequired/__init__.py +29 -0
  17. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +32 -14
  18. mapFolding/someAssemblyRequired/ingredientsNumba.py +22 -1
  19. mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +193 -0
  20. mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +3 -4
  21. mapFolding/someAssemblyRequired/transformDataStructures.py +168 -0
  22. mapFolding/someAssemblyRequired/transformationTools.py +233 -225
  23. mapFolding/theDao.py +19 -5
  24. mapFolding/theSSOT.py +89 -122
  25. mapfolding-0.8.2.dist-info/METADATA +187 -0
  26. mapfolding-0.8.2.dist-info/RECORD +39 -0
  27. {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info}/WHEEL +1 -1
  28. tests/conftest.py +43 -33
  29. tests/test_computations.py +7 -7
  30. tests/test_other.py +2 -2
  31. mapFolding/reference/lunnan.py +0 -153
  32. mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -350
  33. mapFolding/someAssemblyRequired/synthesizeDataConverters.py +0 -117
  34. mapFolding/syntheticModules/numbaCountHistoricalExample.py +0 -158
  35. mapFolding/syntheticModules/numba_doTheNeedfulHistoricalExample.py +0 -13
  36. mapfolding-0.8.0.dist-info/METADATA +0 -157
  37. mapfolding-0.8.0.dist-info/RECORD +0 -41
  38. {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info}/entry_points.txt +0 -0
  39. {mapfolding-0.8.0.dist-info → mapfolding-0.8.2.dist-info/licenses}/LICENSE +0 -0
  40. {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 mapFolding.theSSOT import getAlgorithmDispatcher, getSourceAlgorithm, getPackageDispatcher, theModuleOfSyntheticModules, raiseIfNoneGitHubIssueNumber3
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 pathlib import Path
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
- listCallablesInline = listNumbaCallableDispatchees
204
- callableDispatcher = True
205
- algorithmSource = getSourceAlgorithm()
206
- relativePathWrite = theModuleOfSyntheticModules
207
- filenameModuleWrite = 'pytestCount.py'
208
- formatFilenameWrite = "pytest_{callableTarget}.py"
209
- listSynthesizedModules: list[YouOughtaKnow] = makeFlowNumbaOptimized(listCallablesInline, callableDispatcher, algorithmSource, relativePathWrite, filenameModuleWrite, formatFilenameWrite)
210
- dispatcherSynthetic: YouOughtaKnow | None = None
211
- for stuff in listSynthesizedModules:
212
- registrarRecordsTmpObject(stuff.pathFilenameForMe)
213
- if stuff.callableSynthesized not in listCallablesInline:
214
- dispatcherSynthetic = stuff
215
-
216
- if dispatcherSynthetic is None:
217
- raise raiseIfNoneGitHubIssueNumber3
218
-
219
- dispatcherSpec = importlib.util.spec_from_file_location(dispatcherSynthetic.callableSynthesized, dispatcherSynthetic.pathFilenameForMe)
220
- if dispatcherSpec is None:
221
- raise ImportError(f"{dispatcherSynthetic.pathFilenameForMe=}")
222
- if dispatcherSpec.loader is None:
223
- raise ImportError(f"Failed to get loader for module {dispatcherSynthetic.pathFilenameForMe}")
224
-
225
- dispatcherModule = importlib.util.module_from_spec(dispatcherSpec)
226
- dispatcherSpec.loader.exec_module(dispatcherModule)
227
- callableDispatcherSynthetic = getattr(dispatcherModule, dispatcherSynthetic.callableSynthesized)
228
-
229
- useThisDispatcher(callableDispatcherSynthetic)
230
- return callableDispatcherSynthetic
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."""
@@ -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
- # standardizedEqualTo(str(foldsTotalKnown[tuple(listDimensionsTestCountFolds)]), pathFilenameFoldsTotal.read_text().strip)
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
- from mapFolding.theSSOT import concurrencyPackage
82
- if concurrencyPackage == 'numba':
82
+ if The.concurrencyPackage == 'numba':
83
83
  standardizedEqualToCallableReturn(expectedLimit, setCPUlimit, CPUlimit)
@@ -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()