mapFolding 0.12.2__py3-none-any.whl → 0.13.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 +4 -2
- mapFolding/_theSSOT.py +32 -88
- mapFolding/{datatypes.py → _theTypes.py} +25 -3
- mapFolding/basecamp.py +38 -33
- mapFolding/beDRY.py +79 -54
- mapFolding/dataBaskets.py +123 -93
- mapFolding/filesystemToolkit.py +140 -91
- mapFolding/oeis.py +243 -145
- mapFolding/reference/flattened.py +1 -1
- mapFolding/someAssemblyRequired/RecipeJob.py +116 -100
- mapFolding/someAssemblyRequired/__init__.py +40 -15
- mapFolding/someAssemblyRequired/_toolIfThis.py +82 -54
- mapFolding/someAssemblyRequired/_toolkitContainers.py +19 -16
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +35 -26
- mapFolding/someAssemblyRequired/makeAllModules.py +353 -283
- mapFolding/someAssemblyRequired/makeJobTheorem2Numba.py +83 -84
- mapFolding/someAssemblyRequired/makeJobTheorem2codon.py +256 -0
- mapFolding/someAssemblyRequired/toolkitNumba.py +80 -50
- mapFolding/someAssemblyRequired/transformationTools.py +63 -40
- {tests → mapFolding/tests}/__init__.py +2 -2
- {tests → mapFolding/tests}/conftest.py +232 -63
- {tests → mapFolding/tests}/test_computations.py +58 -18
- {tests → mapFolding/tests}/test_filesystem.py +10 -13
- {tests → mapFolding/tests}/test_oeis.py +5 -18
- {tests → mapFolding/tests}/test_other.py +9 -9
- {tests → mapFolding/tests}/test_tasks.py +7 -9
- {mapfolding-0.12.2.dist-info → mapfolding-0.13.0.dist-info}/METADATA +24 -37
- mapfolding-0.13.0.dist-info/RECORD +54 -0
- {mapfolding-0.12.2.dist-info → mapfolding-0.13.0.dist-info}/top_level.txt +0 -1
- mapfolding-0.12.2.dist-info/RECORD +0 -53
- {mapfolding-0.12.2.dist-info → mapfolding-0.13.0.dist-info}/WHEEL +0 -0
- {mapfolding-0.12.2.dist-info → mapfolding-0.13.0.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.12.2.dist-info → mapfolding-0.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -29,44 +29,30 @@ essential progress feedback capabilities for large-scale computational research.
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
from astToolkit import (
|
|
32
|
-
Be,
|
|
33
|
-
|
|
34
|
-
)
|
|
32
|
+
Be, extractFunctionDef, identifierDotAttribute, IngredientsFunction, IngredientsModule, LedgerOfImports, Make,
|
|
33
|
+
NodeChanger, NodeTourist, Then)
|
|
35
34
|
from astToolkit.transformationTools import write_astModule
|
|
35
|
+
from hunterMakesPy import autoDecodingRLE, raiseIfNone
|
|
36
36
|
from mapFolding import getPathFilenameFoldsTotal, MapFoldingState, packageSettings
|
|
37
37
|
from mapFolding.someAssemblyRequired import IfThis
|
|
38
|
-
from mapFolding.someAssemblyRequired.RecipeJob import
|
|
39
|
-
from mapFolding.someAssemblyRequired.toolkitNumba import
|
|
40
|
-
decorateCallableWithNumba, parametersNumbaLight, SpicesJobNumba,
|
|
41
|
-
)
|
|
38
|
+
from mapFolding.someAssemblyRequired.RecipeJob import RecipeJobTheorem2
|
|
39
|
+
from mapFolding.someAssemblyRequired.toolkitNumba import decorateCallableWithNumba, parametersNumbaLight, SpicesJobNumba
|
|
42
40
|
from mapFolding.syntheticModules.initializeCount import initializeGroupsOfFolds
|
|
43
41
|
from pathlib import PurePosixPath
|
|
44
|
-
from typing import cast, NamedTuple
|
|
45
|
-
from
|
|
42
|
+
from typing import cast, NamedTuple, TYPE_CHECKING
|
|
43
|
+
from typing_extensions import TypeIs
|
|
46
44
|
import ast
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"""Identifiers that are universally unused across all optimization contexts."""
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
from collections.abc import Callable
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
"""Identifiers unused in both parallel and sequential execution modes."""
|
|
49
|
+
listIdentifiersStaticValuesHARDCODED: list[str] = ['dimensionsTotal', 'leavesTotal']
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
"""Identifiers unused specifically in sequential execution mode."""
|
|
57
|
-
|
|
58
|
-
listIdentifiersReplacedHARDCODED: list[str] = ['groupsOfFolds',]
|
|
59
|
-
"""Identifiers that get replaced with optimized equivalents during transformation."""
|
|
60
|
-
|
|
61
|
-
listIdentifiersStaticValuesHARDCODED: list[str] = ['dimensionsTotal', 'leavesTotal',]
|
|
62
|
-
"""Identifiers with compile-time constant values that can be embedded directly."""
|
|
63
|
-
|
|
64
|
-
listIdentifiersNotUsedHARDCODED: list[str] = listIdentifiersStaticValuesHARDCODED + listIdentifiersReplacedHARDCODED + listIdentifiersNotUsedAllHARDCODED + listIdentifiersNotUsedParallelSequentialHARDCODED + listIdentifiersNotUsedSequentialHARDCODED
|
|
65
|
-
"""Complete list of all identifiers that can be eliminated during optimization."""
|
|
66
|
-
|
|
67
|
-
def addLauncherNumbaProgress(ingredientsModule: IngredientsModule, ingredientsFunction: IngredientsFunction, job: RecipeJobTheorem2Numba, spices: SpicesJobNumba) -> tuple[IngredientsModule, IngredientsFunction]:
|
|
51
|
+
def addLauncherNumbaProgress(ingredientsModule: IngredientsModule, ingredientsFunction: IngredientsFunction, job: RecipeJobTheorem2, spices: SpicesJobNumba) -> tuple[IngredientsModule, IngredientsFunction]:
|
|
68
52
|
"""Add progress tracking capabilities to a Numba-optimized function.
|
|
69
53
|
|
|
54
|
+
(AI generated docstring)
|
|
55
|
+
|
|
70
56
|
This function modifies both the module and the function to integrate Numba-compatible
|
|
71
57
|
progress tracking for long-running calculations. It performs several key transformations:
|
|
72
58
|
|
|
@@ -79,16 +65,23 @@ def addLauncherNumbaProgress(ingredientsModule: IngredientsModule, ingredientsFu
|
|
|
79
65
|
which can take hours or days to complete, providing visual feedback and
|
|
80
66
|
estimated completion times.
|
|
81
67
|
|
|
82
|
-
Parameters
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
ingredientsModule : IngredientsModule
|
|
71
|
+
The module where the function is defined.
|
|
72
|
+
ingredientsFunction : IngredientsFunction
|
|
73
|
+
The function to modify with progress tracking.
|
|
74
|
+
job : RecipeJobTheorem2Numba
|
|
75
|
+
Configuration specifying shape details and output paths.
|
|
76
|
+
spices : SpicesJobNumba
|
|
77
|
+
Configuration specifying progress bar details.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
moduleAndFunction : tuple[IngredientsModule, IngredientsFunction]
|
|
89
82
|
Modified module and function with integrated progress tracking capabilities.
|
|
90
|
-
"""
|
|
91
83
|
|
|
84
|
+
"""
|
|
92
85
|
linesLaunch: str = f"""
|
|
93
86
|
if __name__ == '__main__':
|
|
94
87
|
with ProgressBar(total={job.foldsTotalEstimated}, update_interval=2) as statusUpdate:
|
|
@@ -107,9 +100,9 @@ if __name__ == '__main__':
|
|
|
107
100
|
ast_argNumbaProgress = ast.arg(arg=spices.numbaProgressBarIdentifier, annotation=ast.Name(id=numba_progressPythonClass, ctx=ast.Load()))
|
|
108
101
|
ingredientsFunction.astFunctionDef.args.args.append(ast_argNumbaProgress)
|
|
109
102
|
|
|
110
|
-
findThis =
|
|
111
|
-
doThat = Then.replaceWith(Make.Expr(Make.Call(Make.Attribute(Make.Name(spices.numbaProgressBarIdentifier),'update'),[Make.Constant(1)])))
|
|
112
|
-
countWithProgressBar = NodeChanger(findThis, doThat)
|
|
103
|
+
findThis: Callable[[ast.AST], TypeIs[ast.AugAssign] | bool] = Be.AugAssign.targetIs(IfThis.isNameIdentifier(job.shatteredDataclass.countingVariableName.id))
|
|
104
|
+
doThat: Callable[[ast.AugAssign], ast.Expr] = Then.replaceWith(Make.Expr(Make.Call(Make.Attribute(Make.Name(spices.numbaProgressBarIdentifier),'update'),[Make.Constant(1)])))
|
|
105
|
+
countWithProgressBar: NodeChanger[ast.AugAssign, ast.Expr] = NodeChanger(findThis, doThat)
|
|
113
106
|
countWithProgressBar.visit(ingredientsFunction.astFunctionDef)
|
|
114
107
|
|
|
115
108
|
removeReturnStatement = NodeChanger(Be.Return, Then.removeIt)
|
|
@@ -120,9 +113,11 @@ if __name__ == '__main__':
|
|
|
120
113
|
|
|
121
114
|
return ingredientsModule, ingredientsFunction
|
|
122
115
|
|
|
123
|
-
def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: IngredientsFunction, job:
|
|
116
|
+
def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: IngredientsFunction, job: RecipeJobTheorem2) -> IngredientsFunction:
|
|
124
117
|
"""Convert function parameters into initialized variables with concrete values.
|
|
125
118
|
|
|
119
|
+
(AI generated docstring)
|
|
120
|
+
|
|
126
121
|
This function implements a critical transformation that converts function parameters
|
|
127
122
|
into statically initialized variables in the function body. This enables several
|
|
128
123
|
optimizations:
|
|
@@ -136,12 +131,18 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
|
|
|
136
131
|
replacing abstract parameter references with concrete values from the computation state.
|
|
137
132
|
It also removes unused parameters and variables to eliminate dead code.
|
|
138
133
|
|
|
139
|
-
Parameters
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
ingredientsFunction : IngredientsFunction
|
|
137
|
+
The function to transform.
|
|
138
|
+
job : RecipeJobTheorem2Numba
|
|
139
|
+
Recipe containing concrete values for parameters and field metadata.
|
|
142
140
|
|
|
143
|
-
Returns
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
modifiedFunction : IngredientsFunction
|
|
144
144
|
The modified function with parameters converted to initialized variables.
|
|
145
|
+
|
|
145
146
|
"""
|
|
146
147
|
ingredientsFunction.imports.update(job.shatteredDataclass.imports)
|
|
147
148
|
|
|
@@ -160,33 +161,35 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
|
|
|
160
161
|
ImaAnnAssign, elementConstructor = job.shatteredDataclass.Z0Z_field2AnnAssign[ast_arg.arg]
|
|
161
162
|
match elementConstructor:
|
|
162
163
|
case 'scalar':
|
|
163
|
-
cast(ast.Constant, cast(ast.Call, ImaAnnAssign.value).args[0]).value = int(job.state.__dict__[ast_arg.arg])
|
|
164
|
+
cast('ast.Constant', cast('ast.Call', ImaAnnAssign.value).args[0]).value = int(job.state.__dict__[ast_arg.arg])
|
|
164
165
|
case 'array':
|
|
165
|
-
dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg], True)
|
|
166
|
-
dataAs_astExpr: ast.expr = cast(ast.Expr, ast.parse(dataAsStrRLE).body[0]).value
|
|
167
|
-
cast(ast.Call, ImaAnnAssign.value).args = [dataAs_astExpr]
|
|
166
|
+
dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg], assumeAddSpaces=True)
|
|
167
|
+
dataAs_astExpr: ast.expr = cast('ast.Expr', ast.parse(dataAsStrRLE).body[0]).value
|
|
168
|
+
cast('ast.Call', ImaAnnAssign.value).args = [dataAs_astExpr]
|
|
168
169
|
case _:
|
|
169
170
|
list_exprDOTannotation: list[ast.expr] = []
|
|
170
171
|
list_exprDOTvalue: list[ast.expr] = []
|
|
171
172
|
for dimension in job.state.mapShape:
|
|
172
173
|
list_exprDOTannotation.append(Make.Name(elementConstructor))
|
|
173
174
|
list_exprDOTvalue.append(Make.Call(Make.Name(elementConstructor), [Make.Constant(dimension)]))
|
|
174
|
-
cast(ast.Tuple, cast(ast.Subscript, cast(ast.AnnAssign, ImaAnnAssign).annotation).slice).elts = list_exprDOTannotation
|
|
175
|
-
cast(ast.Tuple, ImaAnnAssign.value).elts = list_exprDOTvalue
|
|
175
|
+
cast('ast.Tuple', cast('ast.Subscript', cast('ast.AnnAssign', ImaAnnAssign).annotation).slice).elts = list_exprDOTannotation
|
|
176
|
+
cast('ast.Tuple', ImaAnnAssign.value).elts = list_exprDOTvalue
|
|
176
177
|
|
|
177
178
|
ingredientsFunction.astFunctionDef.body.insert(0, ImaAnnAssign)
|
|
178
179
|
|
|
179
|
-
findThis = IfThis.is_argIdentifier(ast_arg.arg)
|
|
180
|
-
remove_arg = NodeChanger(findThis, Then.removeIt)
|
|
180
|
+
findThis: Callable[[ast.AST], TypeIs[ast.arg] | bool] = IfThis.is_argIdentifier(ast_arg.arg)
|
|
181
|
+
remove_arg: NodeChanger[ast.arg, None] = NodeChanger(findThis, Then.removeIt)
|
|
181
182
|
remove_arg.visit(ingredientsFunction.astFunctionDef)
|
|
182
183
|
|
|
183
184
|
ast.fix_missing_locations(ingredientsFunction.astFunctionDef)
|
|
184
185
|
return ingredientsFunction
|
|
185
186
|
|
|
186
|
-
def makeJobNumba(job:
|
|
187
|
+
def makeJobNumba(job: RecipeJobTheorem2, spices: SpicesJobNumba) -> None:
|
|
187
188
|
"""Generate an optimized Numba-compiled computation module for map folding calculations.
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
(AI generated docstring)
|
|
191
|
+
|
|
192
|
+
This function orchestrates the complete code transformation assembly line to convert
|
|
190
193
|
a generic map folding algorithm into a highly optimized, specialized computation
|
|
191
194
|
module. The transformation process includes:
|
|
192
195
|
|
|
@@ -203,26 +206,21 @@ def makeJobNumba(job: RecipeJobTheorem2Numba, spices: SpicesJobNumba) -> None:
|
|
|
203
206
|
map folding calculations for the specific map dimensions with maximum
|
|
204
207
|
performance through just-in-time compilation.
|
|
205
208
|
|
|
206
|
-
Parameters
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
job : RecipeJobTheorem2Numba
|
|
212
|
+
Configuration recipe containing source locations, target paths, and state.
|
|
213
|
+
spices : SpicesJobNumba
|
|
214
|
+
Optimization settings including Numba parameters and progress options.
|
|
210
215
|
|
|
216
|
+
"""
|
|
211
217
|
astFunctionDef: ast.FunctionDef = raiseIfNone(extractFunctionDef(job.source_astModule, job.countCallable))
|
|
212
218
|
ingredientsCount: IngredientsFunction = IngredientsFunction(astFunctionDef, LedgerOfImports())
|
|
213
219
|
|
|
214
|
-
# Remove `foldGroups` and any other unused statements, so you can dynamically determine which variables are not used
|
|
215
|
-
findThis = ClassIsAndAttribute.targetsIs(ast.Assign, lambda list_expr: any([IfThis.isSubscriptIdentifier('foldGroups')(node) for node in list_expr ]))
|
|
216
|
-
# findThis = IfThis.isAssignAndTargets0Is(IfThis.isSubscriptIdentifier('foldGroups'))
|
|
217
|
-
doThat = Then.removeIt
|
|
218
|
-
remove_foldGroups = NodeChanger(findThis, doThat)
|
|
219
|
-
# remove_foldGroups.visit(ingredientsCount.astFunctionDef)
|
|
220
|
-
|
|
221
|
-
# replace identifiers with static values with their values, so you can dynamically determine which variables are not used
|
|
222
220
|
listIdentifiersStaticValues: list[str] = listIdentifiersStaticValuesHARDCODED
|
|
223
221
|
for identifier in listIdentifiersStaticValues:
|
|
224
|
-
findThis = IfThis.isNameIdentifier(identifier)
|
|
225
|
-
doThat = Then.replaceWith(Make.Constant(int(job.state.__dict__[identifier])))
|
|
222
|
+
findThis: Callable[[ast.AST], TypeIs[ast.Name] | bool] = IfThis.isNameIdentifier(identifier)
|
|
223
|
+
doThat: Callable[[ast.Name], ast.Constant] = Then.replaceWith(Make.Constant(int(job.state.__dict__[identifier])))
|
|
226
224
|
NodeChanger(findThis, doThat).visit(ingredientsCount.astFunctionDef)
|
|
227
225
|
|
|
228
226
|
ingredientsModule = IngredientsModule()
|
|
@@ -242,8 +240,8 @@ if __name__ == '__main__':
|
|
|
242
240
|
writeStream.write(str(foldsTotal))
|
|
243
241
|
writeStream.close()
|
|
244
242
|
"""
|
|
245
|
-
# from mapFolding.oeis import getFoldsTotalKnown
|
|
246
|
-
# print(foldsTotal == getFoldsTotalKnown({job.state.mapShape}))
|
|
243
|
+
# from mapFolding.oeis import getFoldsTotalKnown # noqa: ERA001
|
|
244
|
+
# print(foldsTotal == getFoldsTotalKnown({job.state.mapShape})) # noqa: ERA001
|
|
247
245
|
ingredientsModule.appendLauncher(ast.parse(linesLaunch))
|
|
248
246
|
changeReturnParallelCallable = NodeChanger(Be.Return, Then.replaceWith(Make.Return(job.shatteredDataclass.countingVariableName)))
|
|
249
247
|
changeReturnParallelCallable.visit(ingredientsCount.astFunctionDef)
|
|
@@ -258,12 +256,18 @@ if __name__ == '__main__':
|
|
|
258
256
|
generation. Each configuration specifies the source module, target type name,
|
|
259
257
|
and optional import alias for the transformation.
|
|
260
258
|
|
|
261
|
-
Attributes
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
Attributes
|
|
260
|
+
----------
|
|
261
|
+
fml : str
|
|
262
|
+
Framework datatype identifier to be replaced.
|
|
263
|
+
Z0Z_module : identifierDotAttribute
|
|
264
|
+
Module containing the target datatype (e.g., 'numba', 'numpy').
|
|
265
|
+
Z0Z_type_name : str
|
|
266
|
+
Concrete type name in the target module.
|
|
267
|
+
Z0Z_asname : str | None = None
|
|
268
|
+
Optional import alias for the type.
|
|
266
269
|
"""
|
|
270
|
+
|
|
267
271
|
fml: str
|
|
268
272
|
Z0Z_module: identifierDotAttribute
|
|
269
273
|
Z0Z_type_name: str
|
|
@@ -283,7 +287,7 @@ if __name__ == '__main__':
|
|
|
283
287
|
)
|
|
284
288
|
ingredientsModule.appendPrologue(statement=statement)
|
|
285
289
|
|
|
286
|
-
ingredientsCount.imports.removeImportFromModule('mapFolding.
|
|
290
|
+
ingredientsCount.imports.removeImportFromModule('mapFolding.dataBaskets')
|
|
287
291
|
|
|
288
292
|
listNumPyTypeConfigs = [
|
|
289
293
|
DatatypeConfig(fml='Array1DLeavesTotal', Z0Z_module='numpy', Z0Z_type_name='uint8', Z0Z_asname='Array1DLeavesTotal'),
|
|
@@ -296,7 +300,6 @@ if __name__ == '__main__':
|
|
|
296
300
|
ingredientsCount.imports.addImportFrom_asStr(typeConfig.Z0Z_module, typeConfig.Z0Z_type_name, typeConfig.Z0Z_asname)
|
|
297
301
|
|
|
298
302
|
ingredientsCount.astFunctionDef.decorator_list = [] # TODO low-priority, handle this more elegantly
|
|
299
|
-
# TODO when I add the function signature in numba style back to the decorator, the logic needs to handle `ProgressBarType:`
|
|
300
303
|
ingredientsCount = decorateCallableWithNumba(ingredientsCount, spices.parametersNumba)
|
|
301
304
|
ingredientsModule.appendIngredientsFunction(ingredientsCount)
|
|
302
305
|
write_astModule(ingredientsModule, job.pathFilenameModule, job.packageIdentifier)
|
|
@@ -323,15 +326,11 @@ if __name__ == '__main__':
|
|
|
323
326
|
"""
|
|
324
327
|
|
|
325
328
|
if __name__ == '__main__':
|
|
326
|
-
|
|
327
|
-
state = MapFoldingState(mapShape)
|
|
328
|
-
state = initializeGroupsOfFolds(state)
|
|
329
|
-
# foldsTotalEstimated = getFoldsTotalKnown(state.mapShape) // state.leavesTotal
|
|
330
|
-
# foldsTotalEstimated = dictionaryEstimates[state.mapShape] // state.leavesTotal
|
|
331
|
-
foldsTotalEstimated = 0
|
|
329
|
+
state = initializeGroupsOfFolds(MapFoldingState((2,4)))
|
|
332
330
|
pathModule = PurePosixPath(packageSettings.pathPackage, 'jobs')
|
|
333
331
|
pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(state.mapShape, pathModule))
|
|
334
|
-
aJob =
|
|
332
|
+
aJob = RecipeJobTheorem2(state, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
|
|
335
333
|
spices = SpicesJobNumba(useNumbaProgressBar=False, parametersNumba=parametersNumbaLight)
|
|
336
|
-
# spices = SpicesJobNumba()
|
|
337
334
|
makeJobNumba(aJob, spices)
|
|
335
|
+
|
|
336
|
+
# TODO Improve this module with lessons learned in `makeJobTheorem2codon`.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""codon.
|
|
2
|
+
|
|
3
|
+
https://docs.exaloop.io/start/install/"""
|
|
4
|
+
|
|
5
|
+
from astToolkit import (
|
|
6
|
+
Be, DOT, extractFunctionDef, Grab, hasDOTvalue, identifierDotAttribute, IngredientsFunction, IngredientsModule, Make,
|
|
7
|
+
NodeChanger, NodeTourist, Then)
|
|
8
|
+
from astToolkit.transformationTools import write_astModule
|
|
9
|
+
from hunterMakesPy import autoDecodingRLE, raiseIfNone
|
|
10
|
+
from mapFolding import getPathFilenameFoldsTotal, MapFoldingState, packageSettings
|
|
11
|
+
from mapFolding.someAssemblyRequired import IfThis
|
|
12
|
+
from mapFolding.someAssemblyRequired.RecipeJob import RecipeJobTheorem2
|
|
13
|
+
from mapFolding.syntheticModules.initializeCount import initializeGroupsOfFolds
|
|
14
|
+
from pathlib import Path, PurePosixPath
|
|
15
|
+
from typing import cast, NamedTuple
|
|
16
|
+
import ast
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
class DatatypeConfiguration(NamedTuple):
|
|
21
|
+
"""Configuration for mapping framework datatypes to compiled datatypes.
|
|
22
|
+
|
|
23
|
+
This configuration class defines how abstract datatypes used in the map folding framework should be replaced with compiled
|
|
24
|
+
datatypes during code generation. Each configuration specifies the source module, target type name, and optional import
|
|
25
|
+
alias for the transformation.
|
|
26
|
+
|
|
27
|
+
Attributes
|
|
28
|
+
----------
|
|
29
|
+
datatypeIdentifier : str
|
|
30
|
+
Framework datatype identifier to be replaced.
|
|
31
|
+
typeModule : identifierDotAttribute
|
|
32
|
+
Module containing the target datatype (e.g., 'codon', 'numpy').
|
|
33
|
+
typeIdentifier : str
|
|
34
|
+
Concrete type name in the target module.
|
|
35
|
+
type_asname : str | None = None
|
|
36
|
+
Optional import alias for the type.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
datatypeIdentifier: str
|
|
40
|
+
typeModule: identifierDotAttribute
|
|
41
|
+
typeIdentifier: str
|
|
42
|
+
type_asname: str | None = None
|
|
43
|
+
|
|
44
|
+
# TODO replace with dynamic system. Probably use `Final` in the dataclass.
|
|
45
|
+
listIdentifiersStaticValuesHARDCODED: list[str] = ['dimensionsTotal', 'leavesTotal']
|
|
46
|
+
|
|
47
|
+
listDatatypeConfigs: list[DatatypeConfiguration] = [
|
|
48
|
+
DatatypeConfiguration(datatypeIdentifier='DatatypeLeavesTotal', typeModule='numpy', typeIdentifier='uint16', type_asname='DatatypeLeavesTotal'),
|
|
49
|
+
DatatypeConfiguration(datatypeIdentifier='DatatypeElephino', typeModule='numpy', typeIdentifier='uint16', type_asname='DatatypeElephino'),
|
|
50
|
+
DatatypeConfiguration(datatypeIdentifier='DatatypeFoldsTotal', typeModule='numpy', typeIdentifier='int64', type_asname='DatatypeFoldsTotal'),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
listNumPyTypeConfigs: list[DatatypeConfiguration] = [
|
|
54
|
+
DatatypeConfiguration(datatypeIdentifier='Array1DLeavesTotal', typeModule='numpy', typeIdentifier='uint16', type_asname='Array1DLeavesTotal'),
|
|
55
|
+
DatatypeConfiguration(datatypeIdentifier='Array1DElephino', typeModule='numpy', typeIdentifier='uint16', type_asname='Array1DElephino'),
|
|
56
|
+
DatatypeConfiguration(datatypeIdentifier='Array3D', typeModule='numpy', typeIdentifier='uint16', type_asname='Array3D'),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
def _datatypeDefinitions(ingredientsFunction: IngredientsFunction, ingredientsModule: IngredientsModule) -> tuple[IngredientsFunction, IngredientsModule]:
|
|
60
|
+
for datatypeConfig in listDatatypeConfigs:
|
|
61
|
+
ingredientsFunction.imports.removeImportFrom(datatypeConfig.typeModule, None, datatypeConfig.datatypeIdentifier)
|
|
62
|
+
ingredientsFunction.imports.addImportFrom_asStr(datatypeConfig.typeModule, datatypeConfig.typeIdentifier, datatypeConfig.type_asname)
|
|
63
|
+
continue
|
|
64
|
+
ingredientsModule.appendPrologue(statement=Make.Assign([Make.Name(datatypeConfig.datatypeIdentifier, ast.Store())]
|
|
65
|
+
, value=Make.Name(datatypeConfig.typeIdentifier)
|
|
66
|
+
))
|
|
67
|
+
|
|
68
|
+
for datatypeConfig in listNumPyTypeConfigs:
|
|
69
|
+
ingredientsFunction.imports.removeImportFrom(datatypeConfig.typeModule, None, datatypeConfig.datatypeIdentifier)
|
|
70
|
+
ingredientsFunction.imports.addImportFrom_asStr(datatypeConfig.typeModule, datatypeConfig.typeIdentifier, datatypeConfig.type_asname)
|
|
71
|
+
continue
|
|
72
|
+
ingredientsModule.appendPrologue(statement=Make.Assign([Make.Name(datatypeConfig.datatypeIdentifier, ast.Store())]
|
|
73
|
+
, value=Make.Name(datatypeConfig.typeIdentifier)
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
ingredientsFunction.imports.removeImportFromModule('mapFolding.dataBaskets')
|
|
77
|
+
|
|
78
|
+
return ingredientsFunction, ingredientsModule
|
|
79
|
+
|
|
80
|
+
def _pythonCode2expr(string: str) -> ast.expr:
|
|
81
|
+
"""Convert *one* expression as a string of Python code to an `ast.expr`."""
|
|
82
|
+
return raiseIfNone(NodeTourist(Be.Expr, Then.extractIt(DOT.value)).captureLastMatch(ast.parse(string)))
|
|
83
|
+
|
|
84
|
+
def _variableCompatibility(ingredientsFunction: IngredientsFunction, job: RecipeJobTheorem2) -> IngredientsFunction:
|
|
85
|
+
for ast_arg in job.shatteredDataclass.list_argAnnotated4ArgumentsSpecification:
|
|
86
|
+
identifier = ast_arg.arg
|
|
87
|
+
annotation = raiseIfNone(ast_arg.annotation)
|
|
88
|
+
|
|
89
|
+
# `identifier` in Augmented Assignment.
|
|
90
|
+
NodeChanger(Be.AugAssign.targetIs(IfThis.isNestedNameIdentifier(identifier))
|
|
91
|
+
, doThat=lambda node: Grab.valueAttribute(Then.replaceWith(Make.Call(annotation, listParameters=[node.value])))(node)
|
|
92
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
93
|
+
|
|
94
|
+
# `identifier` in Assignments; exclude `numpy.array`.
|
|
95
|
+
NodeChanger(findThis=lambda node: IfThis.isAssignAndTargets0Is(IfThis.isNameIdentifier(identifier))(node)
|
|
96
|
+
and Be.Assign.valueIs(Be.Constant)(node)
|
|
97
|
+
, doThat=lambda node: Grab.valueAttribute(Then.replaceWith(Make.Call(annotation, listParameters=[node.value])))(node)
|
|
98
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
99
|
+
|
|
100
|
+
# `identifier` - 1.
|
|
101
|
+
NodeChanger(Be.BinOp.leftIs(IfThis.isNestedNameIdentifier(identifier))
|
|
102
|
+
, doThat=lambda node: Grab.rightAttribute(Then.replaceWith(Make.Call(annotation, listParameters=[node.right])))(node)
|
|
103
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
104
|
+
|
|
105
|
+
# `identifier` in Comparison.
|
|
106
|
+
NodeChanger(Be.Compare.leftIs(IfThis.isNestedNameIdentifier(identifier))
|
|
107
|
+
, doThat=lambda node: Grab.comparatorsAttribute(lambda at: Then.replaceWith([Make.Call(annotation, listParameters=[node.comparators[0]])])(at[0]))(node)
|
|
108
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
109
|
+
|
|
110
|
+
# `identifier` has exactly one index value.
|
|
111
|
+
NodeChanger(
|
|
112
|
+
findThis=lambda node: Be.Subscript.valueIs(IfThis.isNestedNameIdentifier(identifier))(node)
|
|
113
|
+
and not Be.Subscript.sliceIs(Be.Tuple)(node)
|
|
114
|
+
, doThat=lambda node: Grab.sliceAttribute(Then.replaceWith(Make.Call(Make.Name('int'), listParameters=[node.slice])))(node)
|
|
115
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
116
|
+
|
|
117
|
+
# `identifier` has multiple index values.
|
|
118
|
+
NodeChanger(
|
|
119
|
+
findThis=lambda node: Be.Subscript.valueIs(IfThis.isNestedNameIdentifier(identifier))(node)
|
|
120
|
+
and Be.Subscript.sliceIs(Be.Tuple)(node)
|
|
121
|
+
, doThat=lambda node: Grab.sliceAttribute(Grab.eltsAttribute(
|
|
122
|
+
Then.replaceWith([
|
|
123
|
+
Make.Call(Make.Name('int'), listParameters=[cast(ast.Tuple, node.slice).elts[index]])
|
|
124
|
+
for index in range(len(cast(ast.Tuple, node.slice).elts))
|
|
125
|
+
])
|
|
126
|
+
))(node)
|
|
127
|
+
).visit(ingredientsFunction.astFunctionDef)
|
|
128
|
+
|
|
129
|
+
return ingredientsFunction
|
|
130
|
+
|
|
131
|
+
def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: IngredientsFunction, job: RecipeJobTheorem2) -> IngredientsFunction:
|
|
132
|
+
"""Convert function parameters into initialized variables with concrete values.
|
|
133
|
+
|
|
134
|
+
This function converts function arguments into statically initialized variables in the function body.
|
|
135
|
+
|
|
136
|
+
The function handles different data types (scalars, arrays, custom types) appropriately, replacing abstract parameter
|
|
137
|
+
references with concrete values from the computation state. It also removes unused parameters and variables.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
ingredientsFunction : IngredientsFunction
|
|
142
|
+
The function to transform.
|
|
143
|
+
job : RecipeJobTheorem2
|
|
144
|
+
Recipe containing concrete values for parameters and field metadata.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
modifiedFunction : IngredientsFunction
|
|
149
|
+
The modified function with parameters converted to initialized variables.
|
|
150
|
+
|
|
151
|
+
"""
|
|
152
|
+
ingredientsFunction.imports.update(job.shatteredDataclass.imports)
|
|
153
|
+
|
|
154
|
+
list_argCuzMyBrainRefusesToThink: list[ast.arg] = ingredientsFunction.astFunctionDef.args.args + ingredientsFunction.astFunctionDef.args.posonlyargs + ingredientsFunction.astFunctionDef.args.kwonlyargs
|
|
155
|
+
list_arg_arg: list[str] = [ast_arg.arg for ast_arg in list_argCuzMyBrainRefusesToThink]
|
|
156
|
+
listName: list[ast.Name] = []
|
|
157
|
+
NodeTourist(Be.Name, Then.appendTo(listName)).visit(ingredientsFunction.astFunctionDef)
|
|
158
|
+
listIdentifiers: list[str] = [astName.id for astName in listName]
|
|
159
|
+
listIdentifiersNotUsed: list[str] = list(set(list_arg_arg) - set(listIdentifiers))
|
|
160
|
+
|
|
161
|
+
for ast_arg in list_argCuzMyBrainRefusesToThink:
|
|
162
|
+
if ast_arg.arg in job.shatteredDataclass.field2AnnAssign:
|
|
163
|
+
if ast_arg.arg in listIdentifiersNotUsed:
|
|
164
|
+
pass
|
|
165
|
+
else:
|
|
166
|
+
Ima___Assign, elementConstructor = job.shatteredDataclass.Z0Z_field2AnnAssign[ast_arg.arg]
|
|
167
|
+
match elementConstructor:
|
|
168
|
+
case 'scalar':
|
|
169
|
+
cast('ast.Constant', cast('ast.Call', Ima___Assign.value).args[0]).value = int(job.state.__dict__[ast_arg.arg])
|
|
170
|
+
case 'array':
|
|
171
|
+
dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg], assumeAddSpaces=True)
|
|
172
|
+
dataAs_ast_expr: ast.expr = _pythonCode2expr(dataAsStrRLE)
|
|
173
|
+
cast('ast.Call', Ima___Assign.value).args = [dataAs_ast_expr]
|
|
174
|
+
case _:
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
ingredientsFunction.astFunctionDef.body.insert(0, Ima___Assign)
|
|
178
|
+
|
|
179
|
+
NodeChanger(IfThis.is_argIdentifier(ast_arg.arg), Then.removeIt).visit(ingredientsFunction.astFunctionDef)
|
|
180
|
+
|
|
181
|
+
ast.fix_missing_locations(ingredientsFunction.astFunctionDef)
|
|
182
|
+
return ingredientsFunction
|
|
183
|
+
|
|
184
|
+
def makeJob(job: RecipeJobTheorem2) -> None:
|
|
185
|
+
"""Generate an optimized module for map folding calculations.
|
|
186
|
+
|
|
187
|
+
This function orchestrates the complete code transformation assembly line to convert
|
|
188
|
+
a generic map folding algorithm into a highly optimized, specialized computation
|
|
189
|
+
module.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
job : RecipeJobTheorem2
|
|
194
|
+
Configuration recipe containing source locations, target paths, and state.
|
|
195
|
+
|
|
196
|
+
"""
|
|
197
|
+
ingredientsCount: IngredientsFunction = IngredientsFunction(raiseIfNone(extractFunctionDef(job.source_astModule, job.countCallable)))
|
|
198
|
+
ingredientsCount.astFunctionDef.decorator_list = []
|
|
199
|
+
|
|
200
|
+
# Replace identifiers-with-static-values with their values.
|
|
201
|
+
listIdentifiersStaticValues: list[str] = listIdentifiersStaticValuesHARDCODED
|
|
202
|
+
for identifier in listIdentifiersStaticValues:
|
|
203
|
+
NodeChanger(IfThis.isNameIdentifier(identifier)
|
|
204
|
+
, Then.replaceWith(Make.Constant(int(job.state.__dict__[identifier])))
|
|
205
|
+
).visit(ingredientsCount.astFunctionDef)
|
|
206
|
+
|
|
207
|
+
linesLaunch: str = f"""
|
|
208
|
+
if __name__ == '__main__':
|
|
209
|
+
foldsTotal = {job.countCallable}()
|
|
210
|
+
print('\\nmap {job.state.mapShape} =', foldsTotal)
|
|
211
|
+
writeStream = open('{job.pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
212
|
+
writeStream.write(str(foldsTotal))
|
|
213
|
+
writeStream.close()
|
|
214
|
+
"""
|
|
215
|
+
ingredientsModule = IngredientsModule(launcher=ast.parse(linesLaunch))
|
|
216
|
+
|
|
217
|
+
# TODO think about `groupsOfFolds *= DatatypeFoldsTotal({2 * job.state.leavesTotal}); return groupsOfFolds`
|
|
218
|
+
NodeChanger(Be.Return
|
|
219
|
+
, Then.replaceWith(Make.Return(job.shatteredDataclass.countingVariableName))).visit(ingredientsCount.astFunctionDef)
|
|
220
|
+
groupsOfFolds2foldsTotal = NodeChanger[ast.AugAssign, hasDOTvalue](
|
|
221
|
+
findThis=(lambda node: Be.AugAssign.targetIs(IfThis.isNameIdentifier(job.shatteredDataclass.countingVariableName.id))(node)
|
|
222
|
+
and Be.AugAssign.opIs(Be.Mult)(node)
|
|
223
|
+
and Be.AugAssign.valueIs(Be.Constant)(node)
|
|
224
|
+
)
|
|
225
|
+
, doThat=lambda node: Grab.valueAttribute(Then.replaceWith(Make.Constant(job.state.leavesTotal * ast.literal_eval(node.value))))(node)
|
|
226
|
+
)
|
|
227
|
+
groupsOfFolds2foldsTotal.visit(ingredientsCount.astFunctionDef)
|
|
228
|
+
|
|
229
|
+
# TODO think about assigning `returns` here, then removing `returns` a few lines from now.
|
|
230
|
+
ingredientsCount.astFunctionDef.returns = job.shatteredDataclass.countingVariableAnnotation
|
|
231
|
+
|
|
232
|
+
ingredientsCount = move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsCount, job)
|
|
233
|
+
|
|
234
|
+
ingredientsCount, ingredientsModule = _datatypeDefinitions(ingredientsCount, ingredientsModule)
|
|
235
|
+
# NOTE Reminder, `returns = None` means "type information is null"; the identifier for the return was removed by `_datatypeDefinitions`.
|
|
236
|
+
ingredientsCount.astFunctionDef.returns = None
|
|
237
|
+
|
|
238
|
+
ingredientsCount = _variableCompatibility(ingredientsCount, job)
|
|
239
|
+
|
|
240
|
+
ingredientsModule.appendIngredientsFunction(ingredientsCount)
|
|
241
|
+
write_astModule(ingredientsModule, pathFilename=job.pathFilenameModule, packageName=job.packageIdentifier)
|
|
242
|
+
|
|
243
|
+
if sys.platform == 'linux':
|
|
244
|
+
pathFilenameBuild = Path.home() / 'mapFolding' / 'jobs' / job.pathFilenameModule.stem
|
|
245
|
+
pathFilenameBuild.parent.mkdir(parents=True, exist_ok=True)
|
|
246
|
+
|
|
247
|
+
buildCommand = ['codon', 'build', '-release', '-disable-exceptions', '-o', str(pathFilenameBuild), str(job.pathFilenameModule)]
|
|
248
|
+
subprocess.run(buildCommand)
|
|
249
|
+
|
|
250
|
+
if __name__ == '__main__':
|
|
251
|
+
state = initializeGroupsOfFolds(MapFoldingState((2,4)))
|
|
252
|
+
pathModule = PurePosixPath(packageSettings.pathPackage, 'jobs')
|
|
253
|
+
# TODO put `pathFilenameFoldsTotal` in wsl.
|
|
254
|
+
pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(state.mapShape, pathModule))
|
|
255
|
+
aJob = RecipeJobTheorem2(state, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
|
|
256
|
+
makeJob(aJob)
|