mapFolding 0.6.0__py3-none-any.whl → 0.7.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 +6 -104
- mapFolding/basecamp.py +12 -8
- mapFolding/beDRY.py +96 -286
- mapFolding/filesystem.py +87 -0
- mapFolding/noHomeYet.py +20 -0
- mapFolding/oeis.py +46 -39
- mapFolding/reference/flattened.py +377 -0
- mapFolding/reference/hunterNumba.py +132 -0
- mapFolding/reference/irvineJavaPort.py +120 -0
- mapFolding/reference/jax.py +208 -0
- mapFolding/reference/lunnan.py +153 -0
- mapFolding/reference/lunnanNumpy.py +123 -0
- mapFolding/reference/lunnanWhile.py +121 -0
- mapFolding/reference/rotatedEntryPoint.py +240 -0
- mapFolding/reference/total_countPlus1vsPlusN.py +211 -0
- mapFolding/someAssemblyRequired/Z0Z_workbench.py +34 -0
- mapFolding/someAssemblyRequired/__init__.py +16 -0
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +21 -0
- mapFolding/someAssemblyRequired/ingredientsNumba.py +100 -0
- mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +7 -0
- mapFolding/someAssemblyRequired/synthesizeDataConverters.py +135 -0
- mapFolding/someAssemblyRequired/synthesizeNumba.py +91 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +417 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +91 -0
- mapFolding/someAssemblyRequired/transformationTools.py +425 -0
- mapFolding/someAssemblyRequired/whatWillBe.py +311 -0
- mapFolding/syntheticModules/__init__.py +0 -0
- mapFolding/syntheticModules/dataNamespaceFlattened.py +30 -0
- mapFolding/syntheticModules/numbaCount.py +90 -0
- mapFolding/syntheticModules/numbaCountExample.py +158 -0
- mapFolding/syntheticModules/numbaCountSequential.py +110 -0
- mapFolding/syntheticModules/numbaCount_doTheNeedful.py +13 -0
- mapFolding/syntheticModules/numba_doTheNeedful.py +12 -0
- mapFolding/syntheticModules/numba_doTheNeedfulExample.py +13 -0
- mapFolding/theDao.py +203 -227
- mapFolding/theSSOT.py +255 -102
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/METADATA +7 -6
- mapfolding-0.7.0.dist-info/RECORD +50 -0
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/WHEEL +1 -1
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/top_level.txt +1 -0
- tests/__init__.py +0 -0
- tests/conftest.py +278 -0
- tests/test_computations.py +49 -0
- tests/test_filesystem.py +52 -0
- tests/test_oeis.py +128 -0
- tests/test_other.py +84 -0
- tests/test_tasks.py +50 -0
- mapFolding/theConfiguration.py +0 -58
- mapFolding/theSSOTdatatypes.py +0 -155
- mapFolding/theWrongWay.py +0 -7
- mapfolding-0.6.0.dist-info/RECORD +0 -16
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/LICENSE +0 -0
- {mapfolding-0.6.0.dist-info → mapfolding-0.7.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"""Synthesize one file to compute `foldsTotal` of `mapShape`."""
|
|
2
|
+
from collections.abc import Sequence
|
|
3
|
+
from typing import Any, cast, TYPE_CHECKING
|
|
4
|
+
from mapFolding.filesystem import getFilenameFoldsTotal, getPathFilenameFoldsTotal
|
|
5
|
+
from mapFolding.someAssemblyRequired import ( ifThis, Make, NodeReplacer, Then, LedgerOfImports, )
|
|
6
|
+
from mapFolding.theSSOT import ( ComputationState, FREAKOUT, getPathJobRootDEFAULT, )
|
|
7
|
+
from os import PathLike
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from Z0Z_tools import autoDecodingRLE
|
|
11
|
+
import ast
|
|
12
|
+
import python_minifier
|
|
13
|
+
import autoflake
|
|
14
|
+
import copy
|
|
15
|
+
import inspect
|
|
16
|
+
import numpy
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from mapFolding.someAssemblyRequired.synthesizeDataConverters import makeStateJob
|
|
19
|
+
from mapFolding.someAssemblyRequired.synthesizeNumba import thisIsNumbaDotJit, decorateCallableWithNumba
|
|
20
|
+
from mapFolding.someAssemblyRequired.whatWillBe import ParametersNumba, Z0Z_getDatatypeModuleScalar, Z0Z_getDecoratorCallable, Z0Z_setDatatypeModuleScalar, Z0Z_setDecoratorCallable, parametersNumbaDEFAULT
|
|
21
|
+
|
|
22
|
+
def Z0Z_gamma(FunctionDefTarget: ast.FunctionDef, astAssignee: ast.Name, statement: ast.Assign | ast.stmt, identifier: str, arrayTarget: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
23
|
+
arrayType = type(arrayTarget)
|
|
24
|
+
moduleConstructor: str = arrayType.__module__
|
|
25
|
+
constructorName: str = arrayType.__name__.replace('ndarray', 'array') # NOTE hack
|
|
26
|
+
dataAsStrRLE: str = autoDecodingRLE(arrayTarget, addSpaces=True)
|
|
27
|
+
dataAs_astExpr: ast.expr = cast(ast.Expr, ast.parse(dataAsStrRLE).body[0]).value
|
|
28
|
+
dtypeName: str = identifier
|
|
29
|
+
dtypeAsName: str = f"{moduleConstructor}_{dtypeName}"
|
|
30
|
+
list_astKeywords: list[ast.keyword] = [ast.keyword(arg='dtype', value=ast.Name(id=dtypeAsName, ctx=ast.Load()))]
|
|
31
|
+
allImports.addImportFromStr(moduleConstructor, dtypeName, dtypeAsName)
|
|
32
|
+
astCall: ast.Call = Make.astCall(Make.astName(constructorName), [dataAs_astExpr], list_astKeywords)
|
|
33
|
+
assignment = ast.Assign(targets=[astAssignee], value=astCall)
|
|
34
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
35
|
+
FunctionDefTarget.body.remove(statement)
|
|
36
|
+
allImports.addImportFromStr(moduleConstructor, constructorName)
|
|
37
|
+
return FunctionDefTarget, allImports
|
|
38
|
+
|
|
39
|
+
def insertArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports, unrollSlices: int | None = None) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
40
|
+
def insertAssign(FunctionDefTarget: ast.FunctionDef, assignee: str, arraySlice: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
41
|
+
statement = ast.Assign(targets=[ast.Name(id='beans', ctx=ast.Load())], value=ast.Constant(value='and cornbread'))
|
|
42
|
+
FunctionDefTarget.body.insert(0, statement)
|
|
43
|
+
astAssignee = ast.Name(id=assignee, ctx=ast.Store())
|
|
44
|
+
return Z0Z_gamma(FunctionDefTarget, astAssignee, statement, identifier, arraySlice, allImports)
|
|
45
|
+
|
|
46
|
+
if not unrollSlices:
|
|
47
|
+
FunctionDefTarget, allImports = insertAssign(FunctionDefTarget, identifier, arrayTarget, allImports)
|
|
48
|
+
else:
|
|
49
|
+
for index, arraySlice in enumerate(arrayTarget):
|
|
50
|
+
FunctionDefTarget, allImports = insertAssign(FunctionDefTarget, f"{identifier}_{index}", arraySlice, allImports)
|
|
51
|
+
|
|
52
|
+
return FunctionDefTarget, allImports
|
|
53
|
+
|
|
54
|
+
def findAndReplaceTrackArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
55
|
+
for statement in FunctionDefTarget.body.copy():
|
|
56
|
+
if ifThis.isUnpackingAnArray(identifier)(statement):
|
|
57
|
+
indexAsStr: str = ast.unparse(statement.value.slice) # type: ignore
|
|
58
|
+
arraySlice: numpy.ndarray[Any, numpy.dtype[numpy.integer[Any]]] = arrayTarget[eval(indexAsStr)]
|
|
59
|
+
astAssignee: ast.Name = cast(ast.Name, statement.targets[0]) # type: ignore
|
|
60
|
+
FunctionDefTarget, allImports = Z0Z_gamma(FunctionDefTarget, astAssignee, statement, identifier, arraySlice, allImports)
|
|
61
|
+
return FunctionDefTarget, allImports
|
|
62
|
+
|
|
63
|
+
def findAndReplaceArraySubscriptIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
64
|
+
# parameter: I define moduleConstructor
|
|
65
|
+
moduleConstructor = Z0Z_getDatatypeModuleScalar()
|
|
66
|
+
|
|
67
|
+
for statement in FunctionDefTarget.body.copy():
|
|
68
|
+
if ifThis.isUnpackingAnArray(identifier)(statement):
|
|
69
|
+
indexAsStr: str = ast.unparse(statement.value.slice) # type: ignore
|
|
70
|
+
arraySlice: numpy.ndarray[Any, numpy.dtype[numpy.integer[Any]]] = arrayTarget[eval(indexAsStr)]
|
|
71
|
+
astAssignee: ast.Name = cast(ast.Name, statement.targets[0]) # type: ignore
|
|
72
|
+
arraySliceItem: int = arraySlice.item()
|
|
73
|
+
constructorName: str = astAssignee.id
|
|
74
|
+
dataAs_astExpr = ast.Constant(value=arraySliceItem)
|
|
75
|
+
list_astKeywords: list[ast.keyword] = []
|
|
76
|
+
astCall: ast.Call = Make.astCall(Make.astName(constructorName), [dataAs_astExpr], list_astKeywords)
|
|
77
|
+
assignment = ast.Assign(targets=[astAssignee], value=astCall)
|
|
78
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
79
|
+
FunctionDefTarget.body.remove(statement)
|
|
80
|
+
allImports.addImportFromStr(moduleConstructor, constructorName)
|
|
81
|
+
return FunctionDefTarget, allImports
|
|
82
|
+
|
|
83
|
+
def removeAssignmentFrom_body(FunctionDefTarget: ast.FunctionDef, identifier: str) -> ast.FunctionDef:
|
|
84
|
+
FunctionDefSherpa: ast.AST | Sequence[ast.AST] | None = NodeReplacer(ifThis.isAnyAssignmentTo(identifier), Then.removeThis).visit(FunctionDefTarget)
|
|
85
|
+
if not FunctionDefSherpa:
|
|
86
|
+
raise FREAKOUT("Dude, where's my function?")
|
|
87
|
+
else:
|
|
88
|
+
FunctionDefTarget = cast(ast.FunctionDef, FunctionDefSherpa)
|
|
89
|
+
ast.fix_missing_locations(FunctionDefTarget)
|
|
90
|
+
return FunctionDefTarget
|
|
91
|
+
|
|
92
|
+
def findAndReplaceAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
93
|
+
"""Unlike most of the other functions, this is generic: it tries to turn an annotation into a construction call."""
|
|
94
|
+
moduleConstructor: str = Z0Z_getDatatypeModuleScalar()
|
|
95
|
+
for stmt in FunctionDefTarget.body.copy():
|
|
96
|
+
if isinstance(stmt, ast.AnnAssign):
|
|
97
|
+
if isinstance(stmt.target, ast.Name) and isinstance(stmt.value, ast.Constant):
|
|
98
|
+
astAssignee: ast.Name = stmt.target
|
|
99
|
+
argData_dtypeName: str = astAssignee.id
|
|
100
|
+
allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
|
|
101
|
+
astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()), args=[stmt.value], keywords=[])
|
|
102
|
+
assignment = ast.Assign(targets=[astAssignee], value=astCall)
|
|
103
|
+
FunctionDefTarget.body.insert(0, assignment)
|
|
104
|
+
FunctionDefTarget.body.remove(stmt)
|
|
105
|
+
return FunctionDefTarget, allImports
|
|
106
|
+
|
|
107
|
+
def findThingyReplaceWithConstantIn_body(FunctionDefTarget: ast.FunctionDef, object: str, value: int) -> ast.FunctionDef:
|
|
108
|
+
"""
|
|
109
|
+
Replaces nodes in astFunction matching the AST of the string `object`
|
|
110
|
+
with a constant node holding the provided value.
|
|
111
|
+
"""
|
|
112
|
+
targetExpression: ast.expr = ast.parse(object, mode='eval').body
|
|
113
|
+
targetDump: str = ast.dump(targetExpression, annotate_fields=False)
|
|
114
|
+
|
|
115
|
+
def findNode(node: ast.AST) -> bool:
|
|
116
|
+
return ast.dump(node, annotate_fields=False) == targetDump
|
|
117
|
+
|
|
118
|
+
def replaceWithConstant(node: ast.AST) -> ast.AST:
|
|
119
|
+
return ast.copy_location(ast.Constant(value=value), node)
|
|
120
|
+
|
|
121
|
+
transformer = NodeReplacer(findNode, replaceWithConstant)
|
|
122
|
+
newFunction: ast.FunctionDef = cast(ast.FunctionDef, transformer.visit(FunctionDefTarget))
|
|
123
|
+
ast.fix_missing_locations(newFunction)
|
|
124
|
+
return newFunction
|
|
125
|
+
|
|
126
|
+
def findAstNameReplaceWithConstantIn_body(FunctionDefTarget: ast.FunctionDef, name: str, value: int) -> ast.FunctionDef:
|
|
127
|
+
def replaceWithConstant(node: ast.AST) -> ast.AST:
|
|
128
|
+
return ast.copy_location(ast.Constant(value=value), node)
|
|
129
|
+
|
|
130
|
+
return cast(ast.FunctionDef, NodeReplacer(ifThis.isName_Identifier(name), replaceWithConstant).visit(FunctionDefTarget))
|
|
131
|
+
|
|
132
|
+
def insertReturnStatementIn_body(FunctionDefTarget: ast.FunctionDef, arrayTarget: numpy.ndarray[tuple[int, ...], numpy.dtype[numpy.integer[Any]]], allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
133
|
+
"""Add multiplication and return statement to function, properly constructing AST nodes."""
|
|
134
|
+
# Create AST for multiplication operation
|
|
135
|
+
multiplicand = 'Z0Z_identifierCountFolds'
|
|
136
|
+
multiplyOperation = ast.BinOp(
|
|
137
|
+
left=ast.Name(id=multiplicand, ctx=ast.Load()),
|
|
138
|
+
op=ast.Mult(), right=ast.Constant(value=int(arrayTarget[-1])))
|
|
139
|
+
|
|
140
|
+
returnStatement = ast.Return(value=multiplyOperation)
|
|
141
|
+
|
|
142
|
+
datatype: str = 'Z0Z_identifierCountFolds'
|
|
143
|
+
FunctionDefTarget.returns = ast.Name(id=datatype, ctx=ast.Load())
|
|
144
|
+
datatypeModuleScalar: str = Z0Z_getDatatypeModuleScalar()
|
|
145
|
+
allImports.addImportFromStr(datatypeModuleScalar, datatype)
|
|
146
|
+
|
|
147
|
+
FunctionDefTarget.body.append(returnStatement)
|
|
148
|
+
|
|
149
|
+
return FunctionDefTarget, allImports
|
|
150
|
+
|
|
151
|
+
def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int) -> ast.FunctionDef:
|
|
152
|
+
"""
|
|
153
|
+
Unroll all nested while loops matching the condition that their test uses `iteratorName`.
|
|
154
|
+
"""
|
|
155
|
+
# Helper transformer to replace iterator occurrences with a constant.
|
|
156
|
+
class ReplaceIterator(ast.NodeTransformer):
|
|
157
|
+
def __init__(self, iteratorName: str, constantValue: int) -> None:
|
|
158
|
+
super().__init__()
|
|
159
|
+
self.iteratorName: str = iteratorName
|
|
160
|
+
self.constantValue: int = constantValue
|
|
161
|
+
|
|
162
|
+
def visit_Name(self, node: ast.Name) -> ast.AST:
|
|
163
|
+
if node.id == self.iteratorName:
|
|
164
|
+
return ast.copy_location(ast.Constant(value=self.constantValue), node)
|
|
165
|
+
return self.generic_visit(node)
|
|
166
|
+
|
|
167
|
+
# NodeTransformer that finds while loops (even if deeply nested) and unrolls them.
|
|
168
|
+
class WhileLoopUnroller(ast.NodeTransformer):
|
|
169
|
+
def __init__(self, iteratorName: str, iterationsTotal: int) -> None:
|
|
170
|
+
super().__init__()
|
|
171
|
+
self.iteratorName: str = iteratorName
|
|
172
|
+
self.iterationsTotal: int = iterationsTotal
|
|
173
|
+
|
|
174
|
+
def visit_While(self, node: ast.While) -> list[ast.stmt]:
|
|
175
|
+
# Check if the while loop's test uses the iterator.
|
|
176
|
+
if isinstance(node.test, ast.Compare) and ifThis.isName_Identifier(self.iteratorName)(node.test.left):
|
|
177
|
+
# Recurse the while loop body and remove AugAssign that increments the iterator.
|
|
178
|
+
cleanBodyStatements: list[ast.stmt] = []
|
|
179
|
+
for loopStatement in node.body:
|
|
180
|
+
# Recursively visit nested statements.
|
|
181
|
+
visitedStatement = self.visit(loopStatement)
|
|
182
|
+
# Remove direct AugAssign: iterator += 1.
|
|
183
|
+
if (isinstance(loopStatement, ast.AugAssign) and
|
|
184
|
+
isinstance(loopStatement.target, ast.Name) and
|
|
185
|
+
loopStatement.target.id == self.iteratorName and
|
|
186
|
+
isinstance(loopStatement.op, ast.Add) and
|
|
187
|
+
isinstance(loopStatement.value, ast.Constant) and
|
|
188
|
+
loopStatement.value.value == 1):
|
|
189
|
+
continue
|
|
190
|
+
cleanBodyStatements.append(visitedStatement)
|
|
191
|
+
|
|
192
|
+
newStatements: list[ast.stmt] = []
|
|
193
|
+
# Unroll using the filtered body.
|
|
194
|
+
for iterationIndex in range(self.iterationsTotal):
|
|
195
|
+
for loopStatement in cleanBodyStatements:
|
|
196
|
+
copiedStatement: ast.stmt = copy.deepcopy(loopStatement)
|
|
197
|
+
replacer = ReplaceIterator(self.iteratorName, iterationIndex)
|
|
198
|
+
newStatement = replacer.visit(copiedStatement)
|
|
199
|
+
ast.fix_missing_locations(newStatement)
|
|
200
|
+
newStatements.append(newStatement)
|
|
201
|
+
# Optionally, process the orelse block.
|
|
202
|
+
if node.orelse:
|
|
203
|
+
for elseStmt in node.orelse:
|
|
204
|
+
visitedElse = self.visit(elseStmt)
|
|
205
|
+
if isinstance(visitedElse, list):
|
|
206
|
+
newStatements.extend(cast(list[ast.stmt], visitedElse))
|
|
207
|
+
else:
|
|
208
|
+
newStatements.append(visitedElse)
|
|
209
|
+
return newStatements
|
|
210
|
+
return [cast(ast.stmt, self.generic_visit(node))]
|
|
211
|
+
|
|
212
|
+
newFunctionDef = WhileLoopUnroller(iteratorName, iterationsTotal).visit(FunctionDefTarget)
|
|
213
|
+
ast.fix_missing_locations(newFunctionDef)
|
|
214
|
+
return newFunctionDef
|
|
215
|
+
|
|
216
|
+
def makeLauncherTqdmJobNumba(callableTarget: str, pathFilenameFoldsTotal: Path, totalEstimated: int, leavesTotal:int) -> ast.Module:
|
|
217
|
+
linesLaunch: str = f"""
|
|
218
|
+
if __name__ == '__main__':
|
|
219
|
+
with ProgressBar(total={totalEstimated}, update_interval=2) as statusUpdate:
|
|
220
|
+
{callableTarget}(statusUpdate)
|
|
221
|
+
foldsTotal = statusUpdate.n * {leavesTotal}
|
|
222
|
+
print("", foldsTotal)
|
|
223
|
+
writeStream = open('{pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
224
|
+
writeStream.write(str(foldsTotal))
|
|
225
|
+
writeStream.close()
|
|
226
|
+
"""
|
|
227
|
+
return ast.parse(linesLaunch)
|
|
228
|
+
|
|
229
|
+
def makeLauncherBasicJobNumba(callableTarget: str, pathFilenameFoldsTotal: Path) -> ast.Module:
|
|
230
|
+
linesLaunch: str = f"""
|
|
231
|
+
if __name__ == '__main__':
|
|
232
|
+
import time
|
|
233
|
+
timeStart = time.perf_counter()
|
|
234
|
+
foldsTotal = {callableTarget}()
|
|
235
|
+
print(foldsTotal, time.perf_counter() - timeStart)
|
|
236
|
+
writeStream = open('{pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
237
|
+
writeStream.write(str(foldsTotal))
|
|
238
|
+
writeStream.close()
|
|
239
|
+
"""
|
|
240
|
+
return ast.parse(linesLaunch)
|
|
241
|
+
|
|
242
|
+
def doUnrollCountGaps(FunctionDefTarget: ast.FunctionDef, stateJob: ComputationState, allImports: LedgerOfImports) -> tuple[ast.FunctionDef, LedgerOfImports]:
|
|
243
|
+
"""The initial results were very bad."""
|
|
244
|
+
FunctionDefTarget = findAndReplaceWhileLoopIn_body(FunctionDefTarget, 'indexDimension', stateJob.dimensionsTotal)
|
|
245
|
+
FunctionDefTarget = removeAssignmentFrom_body(FunctionDefTarget, 'indexDimension')
|
|
246
|
+
FunctionDefTarget = removeAssignmentFrom_body(FunctionDefTarget, 'connectionGraph')
|
|
247
|
+
FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, 'connectionGraph', stateJob.connectionGraph, allImports, stateJob.dimensionsTotal)
|
|
248
|
+
for index in range(stateJob.dimensionsTotal):
|
|
249
|
+
class ReplaceConnectionGraph(ast.NodeTransformer):
|
|
250
|
+
def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
|
|
251
|
+
node = cast(ast.Subscript, self.generic_visit(node))
|
|
252
|
+
if (isinstance(node.value, ast.Name) and node.value.id == "connectionGraph" and
|
|
253
|
+
isinstance(node.slice, ast.Tuple) and len(node.slice.elts) >= 1):
|
|
254
|
+
firstElement: ast.expr = node.slice.elts[0]
|
|
255
|
+
if isinstance(firstElement, ast.Constant) and firstElement.value == index:
|
|
256
|
+
newName = ast.Name(id=f"connectionGraph_{index}", ctx=ast.Load())
|
|
257
|
+
remainingIndices: list[ast.expr] = node.slice.elts[1:]
|
|
258
|
+
if len(remainingIndices) == 1:
|
|
259
|
+
newSlice: ast.expr = remainingIndices[0]
|
|
260
|
+
else:
|
|
261
|
+
newSlice = ast.Tuple(elts=remainingIndices, ctx=ast.Load())
|
|
262
|
+
return ast.copy_location(ast.Subscript(value=newName, slice=newSlice, ctx=node.ctx), node)
|
|
263
|
+
return node
|
|
264
|
+
transformer = ReplaceConnectionGraph()
|
|
265
|
+
FunctionDefTarget = transformer.visit(FunctionDefTarget)
|
|
266
|
+
return FunctionDefTarget, allImports
|
|
267
|
+
|
|
268
|
+
def writeJobNumba(mapShape: Sequence[int], algorithmSource: ModuleType, callableTarget: str | None = None, parametersNumba: ParametersNumba | None = None, pathFilenameWriteJob: str | PathLike[str] | None = None, unrollCountGaps: bool | None = False, Z0Z_totalEstimated: int = 0, **keywordArguments: Any | None) -> Path:
|
|
269
|
+
""" Parameters: **keywordArguments: most especially for `computationDivisions` if you want to make a parallel job. Also `CPUlimit`.
|
|
270
|
+
Notes:
|
|
271
|
+
Hypothetically, everything can now be configured with parameters and functions. And changing how the job is written is relatively easy.
|
|
272
|
+
|
|
273
|
+
Overview
|
|
274
|
+
- the code starts life in theDao.py, which has many optimizations; `makeNumbaOptimizedFlow` increase optimization especially by using numba; `writeJobNumba` increases optimization especially by limiting its capabilities to just one set of parameters
|
|
275
|
+
- the synthesized module must run well as a standalone interpreted-Python script
|
|
276
|
+
- the next major optimization step will (probably) be to use the module synthesized by `writeJobNumba` to compile a standalone executable
|
|
277
|
+
- Nevertheless, at each major optimization step, the code is constantly being improved and optimized, so everything must be well organized and able to handle upstream and downstream changes
|
|
278
|
+
|
|
279
|
+
Minutia
|
|
280
|
+
- perf_counter is for testing. When I run a real job, I delete those lines
|
|
281
|
+
- avoid `with` statement
|
|
282
|
+
|
|
283
|
+
Necessary
|
|
284
|
+
- Move the function's parameters to the function body,
|
|
285
|
+
- initialize identifiers with their state types and values,
|
|
286
|
+
|
|
287
|
+
Optimizations
|
|
288
|
+
- replace static-valued identifiers with their values
|
|
289
|
+
- narrowly focused imports
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
# NOTE get the raw ingredients: data and the algorithm
|
|
293
|
+
stateJob = makeStateJob(mapShape, writeJob=False, **keywordArguments)
|
|
294
|
+
pythonSource: str = inspect.getsource(algorithmSource)
|
|
295
|
+
astModule: ast.Module = ast.parse(pythonSource)
|
|
296
|
+
setFunctionDef: set[ast.FunctionDef] = {statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
|
|
297
|
+
|
|
298
|
+
if not callableTarget:
|
|
299
|
+
if len(setFunctionDef) == 1:
|
|
300
|
+
FunctionDefTarget: ast.FunctionDef | None = setFunctionDef.pop()
|
|
301
|
+
callableTarget = FunctionDefTarget.name
|
|
302
|
+
else:
|
|
303
|
+
raise ValueError(f"I did not receive a `callableTarget` and {algorithmSource.__name__=} has more than one callable: {setFunctionDef}. Please select one.")
|
|
304
|
+
else:
|
|
305
|
+
listFunctionDefTarget: list[ast.FunctionDef] = [statement for statement in setFunctionDef if statement.name == callableTarget]
|
|
306
|
+
FunctionDefTarget = listFunctionDefTarget[0] if listFunctionDefTarget else None
|
|
307
|
+
if not FunctionDefTarget: raise ValueError(f"I received `{callableTarget=}` and {algorithmSource.__name__=}, but I could not find that function in that source.")
|
|
308
|
+
|
|
309
|
+
# NOTE `allImports` is a complementary container to `FunctionDefTarget`; the `FunctionDefTarget` cannot track its own imports very well.
|
|
310
|
+
allImports = LedgerOfImports(astModule)
|
|
311
|
+
|
|
312
|
+
# NOTE remove the parameters from the function signature
|
|
313
|
+
for pirateScowl in FunctionDefTarget.args.args.copy():
|
|
314
|
+
match pirateScowl.arg:
|
|
315
|
+
case 'connectionGraph':
|
|
316
|
+
FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, pirateScowl.arg, stateJob.connectionGraph, allImports)
|
|
317
|
+
case 'gapsWhere':
|
|
318
|
+
FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, pirateScowl.arg, stateJob.gapsWhere, allImports)
|
|
319
|
+
case 'foldGroups':
|
|
320
|
+
FunctionDefTarget = removeAssignmentFrom_body(FunctionDefTarget, pirateScowl.arg)
|
|
321
|
+
case _:
|
|
322
|
+
pass
|
|
323
|
+
FunctionDefTarget.args.args.remove(pirateScowl)
|
|
324
|
+
|
|
325
|
+
identifierCounter = 'Z0Z_identifierCountFolds'
|
|
326
|
+
astExprIncrementCounter = ast.Expr(value = Make.astCall(Make.nameDOTname(identifierCounter, 'update'), args=[ast.Constant(value=1)], list_astKeywords=[]))
|
|
327
|
+
FunctionDefTarget= cast(ast.FunctionDef, NodeReplacer(ifThis.isAugAssignTo(identifierCounter), Then.replaceWith(astExprIncrementCounter)).visit(FunctionDefTarget))
|
|
328
|
+
ast.fix_missing_locations(FunctionDefTarget)
|
|
329
|
+
|
|
330
|
+
for assignmentTarget in ['taskIndex', 'dimensionsTotal', identifierCounter]:
|
|
331
|
+
FunctionDefTarget = removeAssignmentFrom_body(FunctionDefTarget, assignmentTarget)
|
|
332
|
+
# NOTE replace identifiers with static values with their values
|
|
333
|
+
FunctionDefTarget = findAstNameReplaceWithConstantIn_body(FunctionDefTarget, 'dimensionsTotal', int(stateJob.dimensionsTotal))
|
|
334
|
+
FunctionDefTarget = findThingyReplaceWithConstantIn_body(FunctionDefTarget, 'foldGroups[-1]', int(stateJob.foldGroups[-1]))
|
|
335
|
+
|
|
336
|
+
# NOTE an attempt at optimization
|
|
337
|
+
if unrollCountGaps:
|
|
338
|
+
FunctionDefTarget, allImports = doUnrollCountGaps(FunctionDefTarget, stateJob, allImports)
|
|
339
|
+
|
|
340
|
+
# NOTE starting the count and printing the total
|
|
341
|
+
pathFilenameFoldsTotal: Path = getPathFilenameFoldsTotal(stateJob.mapShape)
|
|
342
|
+
|
|
343
|
+
astLauncher: ast.Module = makeLauncherBasicJobNumba(FunctionDefTarget.name, pathFilenameFoldsTotal)
|
|
344
|
+
|
|
345
|
+
# TODO create function for assigning value to `totalEstimated`
|
|
346
|
+
totalEstimated: int = Z0Z_totalEstimated
|
|
347
|
+
astLauncher: ast.Module = makeLauncherTqdmJobNumba(FunctionDefTarget.name, pathFilenameFoldsTotal, totalEstimated, stateJob.foldGroups[-1])
|
|
348
|
+
|
|
349
|
+
allImports.addImportFromStr('numba_progress', 'ProgressBar')
|
|
350
|
+
allImports.addImportFromStr('numba_progress', 'ProgressBarType')
|
|
351
|
+
|
|
352
|
+
# add ProgressBarType parameter to function args
|
|
353
|
+
counterArg = ast.arg(arg=identifierCounter, annotation=ast.Name(id='ProgressBarType', ctx=ast.Load()))
|
|
354
|
+
FunctionDefTarget.args.args.append(counterArg)
|
|
355
|
+
|
|
356
|
+
if parametersNumba is None:
|
|
357
|
+
parametersNumba = parametersNumbaDEFAULT
|
|
358
|
+
parametersNumba['nogil'] = True
|
|
359
|
+
|
|
360
|
+
FunctionDefTarget, allImports = insertReturnStatementIn_body(FunctionDefTarget, stateJob.foldGroups, allImports)
|
|
361
|
+
|
|
362
|
+
FunctionDefTarget, allImports = findAndReplaceAnnAssignIn_body(FunctionDefTarget, allImports)
|
|
363
|
+
# NOTE add the perfect decorator
|
|
364
|
+
FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
|
|
365
|
+
if thisIsNumbaDotJit(FunctionDefTarget.decorator_list[0]):
|
|
366
|
+
astCall: ast.Call = cast(ast.Call, FunctionDefTarget.decorator_list[0])
|
|
367
|
+
astCall.func = ast.Name(id=Z0Z_getDecoratorCallable(), ctx=ast.Load())
|
|
368
|
+
FunctionDefTarget.decorator_list[0] = astCall
|
|
369
|
+
|
|
370
|
+
# NOTE add imports, make str, remove unused imports
|
|
371
|
+
astImports: list[ast.ImportFrom | ast.Import] = allImports.makeListAst()
|
|
372
|
+
astModule = ast.Module(body=cast(list[ast.stmt], astImports + [FunctionDefTarget] + [astLauncher]), type_ignores=[])
|
|
373
|
+
ast.fix_missing_locations(astModule)
|
|
374
|
+
pythonSource = ast.unparse(astModule)
|
|
375
|
+
pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
|
|
376
|
+
pythonSource = python_minifier.minify(pythonSource, remove_annotations = False, remove_pass = False, remove_literal_statements = False, combine_imports = True, hoist_literals = False, rename_locals = False, rename_globals = False, remove_object_base = False, convert_posargs_to_args = False, preserve_shebang = True, remove_asserts = False, remove_debug = False, remove_explicit_return_none = False, remove_builtin_exception_brackets = False, constant_folding = False)
|
|
377
|
+
|
|
378
|
+
# NOTE put on disk
|
|
379
|
+
if pathFilenameWriteJob is None:
|
|
380
|
+
filename: str = getFilenameFoldsTotal(stateJob.mapShape)
|
|
381
|
+
pathRoot: Path = getPathJobRootDEFAULT()
|
|
382
|
+
pathFilenameWriteJob = Path(pathRoot, Path(filename).stem, Path(filename).with_suffix('.py'))
|
|
383
|
+
else:
|
|
384
|
+
pathFilenameWriteJob = Path(pathFilenameWriteJob)
|
|
385
|
+
pathFilenameWriteJob.parent.mkdir(parents=True, exist_ok=True)
|
|
386
|
+
|
|
387
|
+
pathFilenameWriteJob.write_text(pythonSource)
|
|
388
|
+
|
|
389
|
+
return pathFilenameWriteJob
|
|
390
|
+
|
|
391
|
+
if __name__ == '__main__':
|
|
392
|
+
mapShape: list[int] = [5,5]
|
|
393
|
+
dictionaryEstimates: dict[tuple[int, ...], int] = {
|
|
394
|
+
(2,2,2,2,2,2,2,2): 362794844160000,
|
|
395
|
+
(2,21): 1493028892051200,
|
|
396
|
+
(3,15): 9842024675968800,
|
|
397
|
+
(3,3,3,3,3): 85109616000000,
|
|
398
|
+
(3,3,3,3): 85109616000,
|
|
399
|
+
(8,8): 129950723279272000,
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
totalEstimated: int = dictionaryEstimates.get(tuple(mapShape), 10**8)
|
|
403
|
+
from mapFolding.syntheticModules import numbaCount
|
|
404
|
+
algorithmSource: ModuleType = numbaCount
|
|
405
|
+
|
|
406
|
+
callableTarget = 'countSequential'
|
|
407
|
+
|
|
408
|
+
parametersNumba: ParametersNumba = parametersNumbaDEFAULT
|
|
409
|
+
parametersNumba['nogil'] = True
|
|
410
|
+
parametersNumba['boundscheck'] = False
|
|
411
|
+
|
|
412
|
+
pathFilenameWriteJob = None
|
|
413
|
+
|
|
414
|
+
Z0Z_setDatatypeModuleScalar('numba')
|
|
415
|
+
Z0Z_setDecoratorCallable('jit')
|
|
416
|
+
|
|
417
|
+
writeJobNumba(mapShape, algorithmSource, callableTarget, parametersNumba, pathFilenameWriteJob, Z0Z_totalEstimated=totalEstimated)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# pyright: basic
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from mapFolding.someAssemblyRequired.whatWillBe import ParametersSynthesizeNumbaCallable, listNumbaCallableDispatchees
|
|
6
|
+
from mapFolding.theSSOT import theModuleOfSyntheticModules
|
|
7
|
+
from mapFolding.theSSOT import getSourceAlgorithm
|
|
8
|
+
import types
|
|
9
|
+
|
|
10
|
+
def makeFlowNumbaOptimized(listCallablesInline: list[ParametersSynthesizeNumbaCallable] = listNumbaCallableDispatchees, callableDispatcher: bool = True, algorithmSource: types.ModuleType = getSourceAlgorithm(), relativePathWrite: str | PathLike[str] = theModuleOfSyntheticModules, filenameModuleWrite: str = 'filenameModuleSyntheticWrite', formatFilenameWrite: str = 'formatStrFilenameForCallableSynthetic'):
|
|
11
|
+
from mapFolding.someAssemblyRequired.whatWillBe import ParametersSynthesizeNumbaCallable, listNumbaCallableDispatchees
|
|
12
|
+
from mapFolding.someAssemblyRequired.whatWillBe import LedgerOfImports, Z0Z_autoflake_additional_imports
|
|
13
|
+
from mapFolding.theSSOT import FREAKOUT
|
|
14
|
+
from mapFolding.theSSOT import thePathPackage, getDatatypePackage
|
|
15
|
+
from mapFolding.someAssemblyRequired.whatWillBe import FunctionInliner, YouOughtaKnow, ast_Identifier
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import cast
|
|
18
|
+
import ast
|
|
19
|
+
import autoflake
|
|
20
|
+
import inspect
|
|
21
|
+
import warnings
|
|
22
|
+
if relativePathWrite and Path(relativePathWrite).is_absolute():
|
|
23
|
+
raise ValueError("The path to write the module must be relative to the root of the package.")
|
|
24
|
+
|
|
25
|
+
listStuffYouOughtaKnow: list[YouOughtaKnow] = []
|
|
26
|
+
|
|
27
|
+
listFunctionDefs: list[ast.FunctionDef] = []
|
|
28
|
+
allImportsModule = LedgerOfImports()
|
|
29
|
+
for tupleParameters in listCallablesInline:
|
|
30
|
+
pythonSource: str = inspect.getsource(algorithmSource)
|
|
31
|
+
astModule: ast.Module = ast.parse(pythonSource)
|
|
32
|
+
if allImports is None:
|
|
33
|
+
allImports = LedgerOfImports(astModule)
|
|
34
|
+
else:
|
|
35
|
+
allImports.walkThis(astModule)
|
|
36
|
+
|
|
37
|
+
if inlineCallables:
|
|
38
|
+
dictionaryFunctionDef: dict[ast_Identifier, ast.FunctionDef] = {statement.name: statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
|
|
39
|
+
callableInlinerWorkhorse = FunctionInliner(dictionaryFunctionDef)
|
|
40
|
+
FunctionDefTarget = callableInlinerWorkhorse.inlineFunctionBody(callableTarget)
|
|
41
|
+
else:
|
|
42
|
+
FunctionDefTarget = next((statement for statement in astModule.body if isinstance(statement, ast.FunctionDef) and statement.name == callableTarget), None)
|
|
43
|
+
if not FunctionDefTarget:
|
|
44
|
+
raise ValueError(f"Could not find function {callableTarget} in source code")
|
|
45
|
+
|
|
46
|
+
ast.fix_missing_locations(FunctionDefTarget)
|
|
47
|
+
listFunctionDefs.append(FunctionDefTarget)
|
|
48
|
+
allImportsModule.update(allImports)
|
|
49
|
+
|
|
50
|
+
listAstImports: list[ast.ImportFrom | ast.Import] = allImportsModule.makeListAst()
|
|
51
|
+
additional_imports: list[str] = Z0Z_autoflake_additional_imports
|
|
52
|
+
additional_imports.append(getDatatypePackage())
|
|
53
|
+
|
|
54
|
+
astModule = ast.Module(body=cast(list[ast.stmt], listAstImports + listFunctionDefs), type_ignores=[])
|
|
55
|
+
ast.fix_missing_locations(astModule)
|
|
56
|
+
pythonSource: str = ast.unparse(astModule)
|
|
57
|
+
if not pythonSource: raise FREAKOUT
|
|
58
|
+
pythonSource = autoflake.fix_code(pythonSource, additional_imports)
|
|
59
|
+
|
|
60
|
+
pathWrite: Path = thePathPackage / relativePathWrite
|
|
61
|
+
|
|
62
|
+
if not filenameWrite:
|
|
63
|
+
if len(listCallableSynthesized) == 1:
|
|
64
|
+
callableTarget: str = listCallableSynthesized[0].callableTarget
|
|
65
|
+
else:
|
|
66
|
+
callableTarget = filenameWriteCallableTargetDEFAULT
|
|
67
|
+
# NOTE WARNING I think I broken this format string. See theSSOT.py
|
|
68
|
+
filenameWrite = formatFilenameWrite.format(callableTarget=callableTarget)
|
|
69
|
+
else:
|
|
70
|
+
if not filenameWrite.endswith('.py'):
|
|
71
|
+
warnings.warn(f"Filename {filenameWrite=} does not end with '.py'.")
|
|
72
|
+
|
|
73
|
+
pathFilename: Path = pathWrite / filenameWrite
|
|
74
|
+
|
|
75
|
+
pathFilename.write_text(pythonSource)
|
|
76
|
+
|
|
77
|
+
howIsThisStillAThing: Path = thePathPackage.parent
|
|
78
|
+
dumbassPythonNamespace: tuple[str, ...] = pathFilename.relative_to(howIsThisStillAThing).with_suffix('').parts
|
|
79
|
+
ImaModule: str = '.'.join(dumbassPythonNamespace)
|
|
80
|
+
|
|
81
|
+
for item in listCallableSynthesized:
|
|
82
|
+
callableTarget: str = item.callableTarget
|
|
83
|
+
astImportFrom = ast.ImportFrom(module=ImaModule, names=[ast.alias(name=callableTarget, asname=None)], level=0)
|
|
84
|
+
stuff = YouOughtaKnow(callableSynthesized=callableTarget, pathFilenameForMe=pathFilename, astForCompetentProgrammers=astImportFrom)
|
|
85
|
+
listStuffYouOughtaKnow.append(stuff)
|
|
86
|
+
listStuffYouOughtaKnow.extend(listStuff)
|
|
87
|
+
|
|
88
|
+
if callableDispatcher:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
return listStuffYouOughtaKnow
|