mapFolding 0.3.12__py3-none-any.whl → 0.4.1__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 (46) hide show
  1. mapFolding/__init__.py +40 -38
  2. mapFolding/basecamp.py +50 -50
  3. mapFolding/beDRY.py +336 -336
  4. mapFolding/oeis.py +262 -262
  5. mapFolding/reference/flattened.py +294 -293
  6. mapFolding/reference/hunterNumba.py +126 -126
  7. mapFolding/reference/irvineJavaPort.py +99 -99
  8. mapFolding/reference/jax.py +153 -153
  9. mapFolding/reference/lunnan.py +148 -148
  10. mapFolding/reference/lunnanNumpy.py +115 -115
  11. mapFolding/reference/lunnanWhile.py +114 -114
  12. mapFolding/reference/rotatedEntryPoint.py +183 -183
  13. mapFolding/reference/total_countPlus1vsPlusN.py +203 -203
  14. mapFolding/someAssemblyRequired/__init__.py +5 -1
  15. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +12 -12
  16. mapFolding/someAssemblyRequired/makeJob.py +46 -52
  17. mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +17 -17
  18. mapFolding/someAssemblyRequired/synthesizeNumba.py +343 -633
  19. mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +325 -0
  20. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +173 -0
  21. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +77 -0
  22. mapFolding/syntheticModules/__init__.py +0 -0
  23. mapFolding/syntheticModules/numba_countInitialize.py +4 -4
  24. mapFolding/syntheticModules/numba_countParallel.py +4 -4
  25. mapFolding/syntheticModules/numba_countSequential.py +4 -4
  26. mapFolding/syntheticModules/numba_doTheNeedful.py +7 -7
  27. mapFolding/theDao.py +165 -165
  28. mapFolding/theSSOT.py +177 -173
  29. mapFolding/theSSOTnumba.py +90 -74
  30. mapFolding-0.4.1.dist-info/METADATA +154 -0
  31. mapFolding-0.4.1.dist-info/RECORD +42 -0
  32. tests/conftest.py +253 -129
  33. tests/test_computations.py +79 -0
  34. tests/test_oeis.py +76 -85
  35. tests/test_other.py +136 -224
  36. tests/test_tasks.py +19 -23
  37. tests/test_types.py +2 -2
  38. mapFolding/someAssemblyRequired/synthesizeNumbaHardcoding.py +0 -188
  39. mapFolding-0.3.12.dist-info/METADATA +0 -155
  40. mapFolding-0.3.12.dist-info/RECORD +0 -40
  41. tests/conftest_tmpRegistry.py +0 -62
  42. tests/conftest_uniformTests.py +0 -53
  43. {mapFolding-0.3.12.dist-info → mapFolding-0.4.1.dist-info}/LICENSE +0 -0
  44. {mapFolding-0.3.12.dist-info → mapFolding-0.4.1.dist-info}/WHEEL +0 -0
  45. {mapFolding-0.3.12.dist-info → mapFolding-0.4.1.dist-info}/entry_points.txt +0 -0
  46. {mapFolding-0.3.12.dist-info → mapFolding-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,325 @@
1
+ from mapFolding import (
2
+ computationState,
3
+ EnumIndices,
4
+ formatModuleNameDEFAULT,
5
+ FREAKOUT,
6
+ getAlgorithmDispatcher,
7
+ getAlgorithmSource,
8
+ getFilenameFoldsTotal,
9
+ getPathFilenameFoldsTotal,
10
+ getPathJobRootDEFAULT,
11
+ getPathPackage,
12
+ getPathSyntheticModules,
13
+ hackSSOTdatatype,
14
+ indexMy,
15
+ indexTrack,
16
+ moduleOfSyntheticModules,
17
+ myPackageNameIs,
18
+ ParametersNumba,
19
+ parametersNumbaDEFAULT,
20
+ parametersNumbaFailEarly,
21
+ parametersNumbaMinimum,
22
+ parametersNumbaSuperJit,
23
+ parametersNumbaSuperJitParallel,
24
+ setDatatypeElephino,
25
+ setDatatypeFoldsTotal,
26
+ setDatatypeLeavesTotal,
27
+ setDatatypeModule,
28
+ Z0Z_getDatatypeModuleScalar,
29
+ Z0Z_getDecoratorCallable,
30
+ Z0Z_identifierCountFolds,
31
+ Z0Z_setDatatypeModuleScalar,
32
+ Z0Z_setDecoratorCallable,
33
+ )
34
+ from mapFolding.someAssemblyRequired.makeJob import makeStateJob
35
+ from numpy import integer
36
+ from numpy.typing import NDArray
37
+ from types import ModuleType
38
+ from typing import Any, Callable, cast, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
39
+ from Z0Z_tools import autoDecodingRLE
40
+ import ast
41
+ import autoflake
42
+ import collections
43
+ import copy
44
+ import importlib.util
45
+ import inspect
46
+ import more_itertools
47
+ import numba
48
+ import numpy
49
+ import os
50
+ import pathlib
51
+ import python_minifier
52
+
53
+ youOughtaKnow = collections.namedtuple('youOughtaKnow', ['callableSynthesized', 'pathFilenameForMe', 'astForCompetentProgrammers'])
54
+
55
+ # Generic
56
+ class ifThis:
57
+ """Generic AST node predicate builder."""
58
+ @staticmethod
59
+ def nameIs(allegedly: str) -> Callable[[ast.AST], bool]:
60
+ return lambda node: (isinstance(node, ast.Name) and node.id == allegedly)
61
+
62
+ @staticmethod
63
+ def isCallWithAttribute(moduleName: str, callableName: str) -> Callable[[ast.AST], bool]:
64
+ return lambda node: (isinstance(node, ast.Call)
65
+ and isinstance(node.func, ast.Attribute)
66
+ and isinstance(node.func.value, ast.Name)
67
+ and node.func.value.id == moduleName
68
+ and node.func.attr == callableName)
69
+
70
+ @staticmethod
71
+ def isCallWithName(callableName: str) -> Callable[[ast.AST], bool]:
72
+ return lambda node: (isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == callableName)
73
+
74
+ @staticmethod
75
+ def anyOf(*predicates: Callable[[ast.AST], bool]) -> Callable[[ast.AST], bool]:
76
+ return lambda node: any(pred(node) for pred in predicates)
77
+
78
+ class Then:
79
+ """Generic actions."""
80
+ @staticmethod
81
+ def copy_astCallKeywords(astCall: ast.Call) -> Dict[str, Any]:
82
+ """Extract keyword parameters from a decorator AST node."""
83
+ dictionaryKeywords: Dict[str, Any] = {}
84
+ for keywordItem in astCall.keywords:
85
+ if isinstance(keywordItem.value, ast.Constant) and keywordItem.arg is not None:
86
+ dictionaryKeywords[keywordItem.arg] = keywordItem.value.value
87
+ return dictionaryKeywords
88
+
89
+ @staticmethod
90
+ def make_astCall(name: str, args: Optional[Sequence[ast.expr]]=None, list_astKeywords: Optional[Sequence[ast.keyword]]=None, dictionaryKeywords: Optional[Dict[str, Any]]=None) -> ast.Call:
91
+ list_dictionaryKeywords = [ast.keyword(arg=keyName, value=ast.Constant(value=keyValue)) for keyName, keyValue in dictionaryKeywords.items()] if dictionaryKeywords else []
92
+ return ast.Call(
93
+ func=ast.Name(id=name, ctx=ast.Load()),
94
+ args=list(args) if args else [],
95
+ keywords=list_dictionaryKeywords + list(list_astKeywords) if list_astKeywords else [],
96
+ )
97
+
98
+ class NodeReplacer(ast.NodeTransformer):
99
+ """
100
+ A node transformer that replaces or removes AST nodes based on a condition.
101
+ This transformer traverses an AST and for each node checks a predicate. If the predicate
102
+ returns True, the transformer uses the replacement builder to obtain a new node. Returning
103
+ None from the replacement builder indicates that the node should be removed.
104
+
105
+ Attributes:
106
+ findMe (Callable[[ast.AST], bool]): A function that determines whether a node should be replaced.
107
+ nodeReplacementBuilder (Callable[[ast.AST], Optional[ast.AST]]): A function that returns a new node
108
+ or None to remove the node.
109
+
110
+ Methods:
111
+ visit(node: ast.AST) -> Optional[ast.AST]:
112
+ Visits each node in the AST, replacing or removing it based on the predicate.
113
+ """
114
+ def __init__(self, findMe: Callable[[ast.AST], bool], nodeReplacementBuilder: Callable[[ast.AST], Optional[ast.AST]]) -> None:
115
+ self.findMe = findMe
116
+ self.nodeReplacementBuilder = nodeReplacementBuilder
117
+
118
+ def visit(self, node: ast.AST) -> ast.AST | None | Any:
119
+ if self.findMe(node):
120
+ return self.nodeReplacementBuilder(node)
121
+ return super().visit(node)
122
+
123
+ # Confusing: suspiciously specific but still reusable
124
+ def thisIsNumbaDotJit(Ima: ast.AST) -> bool:
125
+ return ifThis.isCallWithAttribute(Z0Z_getDatatypeModuleScalar(), Z0Z_getDecoratorCallable())(Ima)
126
+
127
+ def thisIsJit(Ima: ast.AST) -> bool:
128
+ return ifThis.isCallWithName(Z0Z_getDecoratorCallable())(Ima)
129
+
130
+ def thisIsAnyNumbaJitDecorator(Ima: ast.AST) -> bool:
131
+ return thisIsNumbaDotJit(Ima) or thisIsJit(Ima)
132
+
133
+ # Domain-based
134
+ class UniversalImportTracker:
135
+ def __init__(self) -> None:
136
+ self.dictionaryImportFrom: Dict[str, Set] = collections.defaultdict(set)
137
+ self.setImport = set()
138
+
139
+ def addAst(self, astImport_: Union[ast.Import, ast.ImportFrom]) -> None:
140
+ if isinstance(astImport_, ast.Import):
141
+ for alias in astImport_.names:
142
+ self.setImport.add(alias.name)
143
+ elif isinstance(astImport_, ast.ImportFrom):
144
+ if astImport_.module is not None:
145
+ self.dictionaryImportFrom[astImport_.module].update(alias.name for alias in astImport_.names)
146
+
147
+ def addImportFromStr(self, module: str, name: str) -> None:
148
+ self.dictionaryImportFrom[module].add(name)
149
+
150
+ def addImportStr(self, name: str) -> None:
151
+ self.setImport.add(name)
152
+
153
+ def makeListAst(self) -> List[Union[ast.ImportFrom, ast.Import]]:
154
+ listAstImportFrom = [ast.ImportFrom(module=module, names=[ast.alias(name=name, asname=None)], level=0) for module, names in self.dictionaryImportFrom.items() for name in names]
155
+ listAstImport = [ast.Import(names=[ast.alias(name=name, asname=None)]) for name in self.setImport]
156
+ return listAstImportFrom + listAstImport
157
+
158
+ # Intricate and specialized
159
+ class RecursiveInliner(ast.NodeTransformer):
160
+ """
161
+ Class RecursiveInliner:
162
+ A custom AST NodeTransformer designed to recursively inline function calls from a given dictionary
163
+ of function definitions into the AST. Once a particular function has been inlined, it is marked
164
+ as completed to avoid repeated inlining. This transformation modifies the AST in-place by substituting
165
+ eligible function calls with the body of their corresponding function.
166
+ Attributes:
167
+ dictionaryFunctions (Dict[str, ast.FunctionDef]):
168
+ A mapping of function name to its AST definition, used as a source for inlining.
169
+ callablesCompleted (Set[str]):
170
+ A set to track function names that have already been inlined to prevent multiple expansions.
171
+ Methods:
172
+ inlineFunctionBody(callableTargetName: str) -> Optional[ast.FunctionDef]:
173
+ Retrieves the AST definition for a given function name from dictionaryFunctions
174
+ and recursively inlines any function calls within it. Returns the function definition
175
+ that was inlined or None if the function was already processed.
176
+ visit_Call(callNode: ast.Call) -> ast.AST:
177
+ Inspects calls within the AST. If a function call matches one in dictionaryFunctions,
178
+ it is replaced by the inlined body. If the last statement in the inlined body is a return
179
+ or an expression, that value or expression is substituted; otherwise, a constant is returned.
180
+ visit_Expr(node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
181
+ Handles expression nodes in the AST. If the expression is a function call from
182
+ dictionaryFunctions, its statements are expanded in place, effectively inlining
183
+ the called function's statements into the surrounding context.
184
+ """
185
+ def __init__(self, dictionaryFunctions: Dict[str, ast.FunctionDef]):
186
+ self.dictionaryFunctions = dictionaryFunctions
187
+ self.callablesCompleted: Set[str] = set()
188
+
189
+ def inlineFunctionBody(self, callableTargetName: str) -> Optional[ast.FunctionDef]:
190
+ if (callableTargetName in self.callablesCompleted):
191
+ return None
192
+
193
+ self.callablesCompleted.add(callableTargetName)
194
+ inlineDefinition = self.dictionaryFunctions[callableTargetName]
195
+ for astNode in ast.walk(inlineDefinition):
196
+ self.visit(astNode)
197
+ return inlineDefinition
198
+
199
+ def visit_Call(self, node: ast.Call) -> Any | ast.Constant | ast.Call | ast.AST:
200
+ callNodeVisited = self.generic_visit(node)
201
+ if (isinstance(callNodeVisited, ast.Call) and isinstance(callNodeVisited.func, ast.Name) and callNodeVisited.func.id in self.dictionaryFunctions):
202
+ inlineDefinition = self.inlineFunctionBody(callNodeVisited.func.id)
203
+ if (inlineDefinition and inlineDefinition.body):
204
+ statementTerminating = inlineDefinition.body[-1]
205
+ if (isinstance(statementTerminating, ast.Return) and statementTerminating.value is not None):
206
+ return self.visit(statementTerminating.value)
207
+ elif (isinstance(statementTerminating, ast.Expr) and statementTerminating.value is not None):
208
+ return self.visit(statementTerminating.value)
209
+ return ast.Constant(value=None)
210
+ return callNodeVisited
211
+
212
+ def visit_Expr(self, node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
213
+ if (isinstance(node.value, ast.Call)):
214
+ if (isinstance(node.value.func, ast.Name) and node.value.func.id in self.dictionaryFunctions):
215
+ inlineDefinition = self.inlineFunctionBody(node.value.func.id)
216
+ if (inlineDefinition):
217
+ return [self.visit(stmt) for stmt in inlineDefinition.body]
218
+ return self.generic_visit(node)
219
+
220
+ class UnpackArrays(ast.NodeTransformer):
221
+ """
222
+ A class that transforms array accesses using enum indices into local variables.
223
+
224
+ This AST transformer identifies array accesses using enum indices and replaces them
225
+ with local variables, adding initialization statements at the start of functions.
226
+
227
+ Parameters:
228
+ enumIndexClass (Type[EnumIndices]): The enum class used for array indexing
229
+ arrayName (str): The name of the array being accessed
230
+
231
+ Attributes:
232
+ enumIndexClass (Type[EnumIndices]): Stored enum class for index lookups
233
+ arrayName (str): Name of the array being transformed
234
+ substitutions (dict): Tracks variable substitutions and their original nodes
235
+
236
+ The transformer handles two main cases:
237
+ 1. Scalar array access - array[EnumIndices.MEMBER]
238
+ 2. Array slice access - array[EnumIndices.MEMBER, other_indices...]
239
+ For each identified access pattern, it:
240
+ 1. Creates a local variable named after the enum member
241
+ 2. Adds initialization code at function start
242
+ 3. Replaces original array access with the local variable
243
+ """
244
+
245
+ def __init__(self, enumIndexClass: Type[EnumIndices], arrayName: str) -> None:
246
+ self.enumIndexClass = enumIndexClass
247
+ self.arrayName = arrayName
248
+ self.substitutions: Dict[str, Any] = {}
249
+
250
+ def extract_member_name(self, node: ast.AST) -> Optional[str]:
251
+ """Recursively extract enum member name from any node in the AST."""
252
+ if isinstance(node, ast.Attribute) and node.attr == 'value':
253
+ innerAttribute = node.value
254
+ while isinstance(innerAttribute, ast.Attribute):
255
+ if (isinstance(innerAttribute.value, ast.Name) and innerAttribute.value.id == self.enumIndexClass.__name__):
256
+ return innerAttribute.attr
257
+ innerAttribute = innerAttribute.value
258
+ return None
259
+
260
+ def transform_slice_element(self, node: ast.AST) -> ast.AST:
261
+ """Transform any enum references within a slice element."""
262
+ if isinstance(node, ast.Subscript):
263
+ if isinstance(node.slice, ast.Attribute):
264
+ member_name = self.extract_member_name(node.slice)
265
+ if member_name:
266
+ return ast.Name(id=member_name, ctx=node.ctx)
267
+ elif isinstance(node, ast.Tuple):
268
+ # Handle tuple slices by transforming each element
269
+ return ast.Tuple(elts=cast(List[ast.expr], [self.transform_slice_element(elt) for elt in node.elts]), ctx=node.ctx)
270
+ elif isinstance(node, ast.Attribute):
271
+ member_name = self.extract_member_name(node)
272
+ if member_name:
273
+ return ast.Name(id=member_name, ctx=ast.Load())
274
+ return node
275
+
276
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
277
+ # Recursively visit any nested subscripts in value or slice
278
+ node.value = self.visit(node.value)
279
+ node.slice = self.visit(node.slice)
280
+ # If node.value is not our arrayName, just return node
281
+ if not (isinstance(node.value, ast.Name) and node.value.id == self.arrayName):
282
+ return node
283
+
284
+ # Handle scalar array access
285
+ if isinstance(node.slice, ast.Attribute):
286
+ memberName = self.extract_member_name(node.slice)
287
+ if memberName:
288
+ self.substitutions[memberName] = ('scalar', node)
289
+ return ast.Name(id=memberName, ctx=ast.Load())
290
+
291
+ # Handle array slice access
292
+ if isinstance(node.slice, ast.Tuple) and node.slice.elts:
293
+ firstElement = node.slice.elts[0]
294
+ memberName = self.extract_member_name(firstElement)
295
+ sliceRemainder = [self.visit(elem) for elem in node.slice.elts[1:]]
296
+ if memberName:
297
+ self.substitutions[memberName] = ('array', node)
298
+ if len(sliceRemainder) == 0:
299
+ return ast.Name(id=memberName, ctx=ast.Load())
300
+ return ast.Subscript(value=ast.Name(id=memberName, ctx=ast.Load()), slice=ast.Tuple(elts=sliceRemainder, ctx=ast.Load()) if len(sliceRemainder) > 1 else sliceRemainder[0], ctx=ast.Load())
301
+
302
+ # If single-element tuple, unwrap
303
+ if isinstance(node.slice, ast.Tuple) and len(node.slice.elts) == 1:
304
+ node.slice = node.slice.elts[0]
305
+
306
+ return node
307
+
308
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
309
+ node = cast(ast.FunctionDef, self.generic_visit(node))
310
+
311
+ initializations = []
312
+ for name, (kind, original_node) in self.substitutions.items():
313
+ if kind == 'scalar':
314
+ initializations.append(ast.Assign(targets=[ast.Name(id=name, ctx=ast.Store())], value=original_node))
315
+ else: # array
316
+ initializations.append(
317
+ ast.Assign(
318
+ targets=[ast.Name(id=name, ctx=ast.Store())],
319
+ value=ast.Subscript(value=ast.Name(id=self.arrayName, ctx=ast.Load()),
320
+ slice=ast.Attribute(value=ast.Attribute(
321
+ value=ast.Name(id=self.enumIndexClass.__name__, ctx=ast.Load()),
322
+ attr=name, ctx=ast.Load()), attr='value', ctx=ast.Load()), ctx=ast.Load())))
323
+
324
+ node.body = initializations + node.body
325
+ return node
@@ -0,0 +1,173 @@
1
+ """Synthesize one file to compute `foldsTotal` of `mapShape`."""
2
+ from mapFolding.someAssemblyRequired.synthesizeNumba import *
3
+
4
+ def doUnrollCountGaps(FunctionDefTarget: ast.FunctionDef, stateJob: computationState, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
5
+ """The initial results were very bad."""
6
+ FunctionDefTarget = findAndReplaceWhileLoopIn_body(FunctionDefTarget, 'indexDimension', stateJob['my'][indexMy.dimensionsTotal])
7
+ FunctionDefTarget = removeAssignTargetFrom_body(FunctionDefTarget, 'indexDimension')
8
+ FunctionDefTarget = removeAssignTargetFrom_body(FunctionDefTarget, 'connectionGraph')
9
+ FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, 'connectionGraph', stateJob['connectionGraph'], allImports, stateJob['my'][indexMy.dimensionsTotal])
10
+ for index in range(stateJob['my'][indexMy.dimensionsTotal]):
11
+ class ReplaceConnectionGraph(ast.NodeTransformer):
12
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
13
+ node = cast(ast.Subscript, self.generic_visit(node))
14
+ if (isinstance(node.value, ast.Name) and node.value.id == "connectionGraph" and
15
+ isinstance(node.slice, ast.Tuple) and len(node.slice.elts) >= 1):
16
+ firstElement = node.slice.elts[0]
17
+ if isinstance(firstElement, ast.Constant) and firstElement.value == index:
18
+ newName = ast.Name(id=f"connectionGraph_{index}", ctx=ast.Load())
19
+ remainingIndices = node.slice.elts[1:]
20
+ if len(remainingIndices) == 1:
21
+ newSlice = remainingIndices[0]
22
+ else:
23
+ newSlice = ast.Tuple(elts=remainingIndices, ctx=ast.Load())
24
+ return ast.copy_location(ast.Subscript(value=newName, slice=newSlice, ctx=node.ctx), node)
25
+ return node
26
+ transformer = ReplaceConnectionGraph()
27
+ FunctionDefTarget = transformer.visit(FunctionDefTarget)
28
+ return FunctionDefTarget, allImports
29
+
30
+ def writeJobNumba(mapShape: Sequence[int]
31
+ , algorithmSource: ModuleType
32
+ , callableTarget: Optional[str] = None
33
+ , parametersNumba: Optional[ParametersNumba] = None
34
+ , pathFilenameWriteJob: Optional[Union[str, os.PathLike[str]]] = None
35
+ , unrollCountGaps: Optional[bool] = False
36
+ , **keywordArguments: Optional[Any]
37
+ ) -> pathlib.Path:
38
+ """ Parameters: **keywordArguments: most especially for `computationDivisions` if you want to make a parallel job. Also `CPUlimit`. """
39
+
40
+ """ Notes:
41
+ Hypothetically, everything can now be configured with parameters and functions. And changing how the job is written is relatively easy.
42
+
43
+ Overview
44
+ - 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
45
+ - the synthesized module must run well as a standalone interpreted-Python script
46
+ - the next major optimization step will (probably) be to use the module synthesized by `writeJobNumba` to compile a standalone executable
47
+ - 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
48
+
49
+ Minutia
50
+ - perf_counter is for testing. When I run a real job, I delete those lines
51
+ - avoid `with` statement
52
+
53
+ Necessary
54
+ - Move the function's parameters to the function body,
55
+ - initialize identifiers with their state types and values,
56
+
57
+ Optimizations
58
+ - replace static-valued identifiers with their values
59
+ - narrowly focused imports
60
+ """
61
+
62
+ # NOTE get the raw ingredients: data and the algorithm
63
+ stateJob = makeStateJob(mapShape, writeJob=False, **keywordArguments)
64
+ pythonSource = inspect.getsource(algorithmSource)
65
+ astModule = ast.parse(pythonSource)
66
+ setFunctionDef = {statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
67
+ if not callableTarget:
68
+ if len(setFunctionDef) == 1:
69
+ FunctionDefTarget = setFunctionDef.pop()
70
+ callableTarget = FunctionDefTarget.name
71
+ else:
72
+ raise ValueError(f"I did not receive a `callableTarget` and {algorithmSource.__name__=} has more than one callable: {setFunctionDef}. Please select one.")
73
+ else:
74
+ FunctionDefTarget = setFunctionDef.pop() if callableTarget in {statement.name for statement in setFunctionDef} else None
75
+ if not FunctionDefTarget: raise ValueError(f"I received `{callableTarget=}` and {algorithmSource.__name__=}, but I could not find that function in that source.")
76
+
77
+ # NOTE `allImports` is a complementary container to `FunctionDefTarget`; the `FunctionDefTarget` cannot track its own imports very well.
78
+ allImports = UniversalImportTracker()
79
+ for statement in astModule.body:
80
+ if isinstance(statement, (ast.Import, ast.ImportFrom)):
81
+ allImports.addAst(statement)
82
+
83
+ # NOTE remove the parameters from the function signature
84
+ for pirateScowl in FunctionDefTarget.args.args.copy():
85
+ match pirateScowl.arg:
86
+ case 'my':
87
+ FunctionDefTarget, allImports = findAndReplaceArraySubscriptIn_body(FunctionDefTarget, pirateScowl.arg, stateJob[pirateScowl.arg], ['taskIndex', 'dimensionsTotal'], allImports)
88
+ case 'track':
89
+ FunctionDefTarget, allImports = findAndReplaceArrayIn_body(FunctionDefTarget, pirateScowl.arg, stateJob[pirateScowl.arg], allImports)
90
+ case 'connectionGraph':
91
+ FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, pirateScowl.arg, stateJob[pirateScowl.arg], allImports)
92
+ case 'gapsWhere':
93
+ FunctionDefTarget, allImports = insertArrayIn_body(FunctionDefTarget, pirateScowl.arg, stateJob[pirateScowl.arg], allImports)
94
+ case 'foldGroups':
95
+ FunctionDefTarget = removeAssignTargetFrom_body(FunctionDefTarget, pirateScowl.arg)
96
+ FunctionDefTarget.args.args.remove(pirateScowl)
97
+
98
+ # NOTE replace identifiers with static values with their values
99
+ FunctionDefTarget, allImports = findAndReplaceAnnAssignIn_body(FunctionDefTarget, allImports)
100
+ FunctionDefTarget = findAstNameReplaceWithConstantIn_body(FunctionDefTarget, 'dimensionsTotal', int(stateJob['my'][indexMy.dimensionsTotal]))
101
+ FunctionDefTarget = findThingyReplaceWithConstantIn_body(FunctionDefTarget, 'foldGroups[-1]', int(stateJob['foldGroups'][-1]))
102
+
103
+ # NOTE an attempt at optimization
104
+ if unrollCountGaps:
105
+ FunctionDefTarget, allImports = doUnrollCountGaps(FunctionDefTarget, stateJob, allImports)
106
+
107
+ # NOTE starting the count and printing the total
108
+ pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateJob['mapShape'])
109
+ astLauncher = makeLauncherBasicJobNumba(FunctionDefTarget.name, pathFilenameFoldsTotal)
110
+ FunctionDefTarget, allImports = insertReturnStatementIn_body(FunctionDefTarget, stateJob['foldGroups'], allImports)
111
+
112
+ # NOTE add the perfect decorator
113
+ datatype = hackSSOTdatatype(Z0Z_identifierCountFolds)
114
+ FunctionDefTarget.returns = ast.Name(id=datatype, ctx=ast.Load())
115
+ FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
116
+ if thisIsNumbaDotJit(FunctionDefTarget.decorator_list[0]):
117
+ astCall = cast(ast.Call, FunctionDefTarget.decorator_list[0])
118
+ astCall.func = ast.Name(id=Z0Z_getDecoratorCallable(), ctx=ast.Load())
119
+ FunctionDefTarget.decorator_list[0] = astCall
120
+
121
+ # NOTE add imports, make str, remove unused imports
122
+ astImports = allImports.makeListAst()
123
+ astModule = ast.Module(body=cast(List[ast.stmt], astImports + [FunctionDefTarget] + [astLauncher]), type_ignores=[])
124
+ ast.fix_missing_locations(astModule)
125
+ pythonSource = ast.unparse(astModule)
126
+ pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
127
+ pythonSource = python_minifier.minify(pythonSource, remove_annotations = False,
128
+ remove_pass = False,
129
+ remove_literal_statements = False,
130
+ combine_imports = True,
131
+ hoist_literals = False,
132
+ rename_locals = False,
133
+ rename_globals = False,
134
+ remove_object_base = False,
135
+ convert_posargs_to_args = False,
136
+ preserve_shebang = True,
137
+ remove_asserts = False,
138
+ remove_debug = False,
139
+ remove_explicit_return_none = False,
140
+ remove_builtin_exception_brackets = False,
141
+ constant_folding = False)
142
+
143
+ # NOTE put on disk
144
+ if pathFilenameWriteJob is None:
145
+ filename = getFilenameFoldsTotal(stateJob['mapShape'])
146
+ pathRoot = getPathJobRootDEFAULT()
147
+ pathFilenameWriteJob = pathlib.Path(pathRoot, pathlib.Path(filename).stem, pathlib.Path(filename).with_suffix('.py'))
148
+ else:
149
+ pathFilenameWriteJob = pathlib.Path(pathFilenameWriteJob)
150
+ pathFilenameWriteJob.parent.mkdir(parents=True, exist_ok=True)
151
+
152
+ pathFilenameWriteJob.write_text(pythonSource)
153
+
154
+ return pathFilenameWriteJob
155
+
156
+ if __name__ == '__main__':
157
+ mapShape = [5,5]
158
+ from mapFolding.syntheticModules import numba_countSequential
159
+ algorithmSource: ModuleType = numba_countSequential
160
+
161
+ callableTarget = None
162
+
163
+ parametersNumba = parametersNumbaDEFAULT
164
+
165
+ pathFilenameWriteJob = None
166
+
167
+ setDatatypeFoldsTotal('int64', sourGrapes=True)
168
+ setDatatypeElephino('uint8', sourGrapes=True)
169
+ setDatatypeLeavesTotal('uint8', sourGrapes=True)
170
+ Z0Z_setDatatypeModuleScalar('numba')
171
+ Z0Z_setDecoratorCallable('jit')
172
+
173
+ writeJobNumba(mapShape, algorithmSource, callableTarget, parametersNumba, pathFilenameWriteJob)
@@ -0,0 +1,77 @@
1
+ """I suspect this function will be relatively stable for now.
2
+ Managing settings and options, however, ... I've 'invented'
3
+ everything I am doing. I would rather benefit from humanity's
4
+ collective wisdom."""
5
+ from mapFolding.someAssemblyRequired.synthesizeNumba import *
6
+
7
+ def makeFlowNumbaOptimized(listCallablesInline: List[str], callableDispatcher: Optional[bool] = False, algorithmSource: Optional[ModuleType] = None, relativePathWrite: Optional[pathlib.Path] = None, formatFilenameWrite: Optional[str] = None) -> List[youOughtaKnow]:
8
+ if relativePathWrite and relativePathWrite.is_absolute():
9
+ raise ValueError("The path to write the module must be relative to the root of the package.")
10
+ if not algorithmSource:
11
+ algorithmSource = getAlgorithmSource()
12
+
13
+ listStuffYouOughtaKnow: List[youOughtaKnow] = []
14
+
15
+ def doThisStuff(callableTarget: str, parametersNumba: Optional[ParametersNumba], inlineCallables: bool, unpackArrays: bool, allImports: Optional[UniversalImportTracker], relativePathWrite: Optional[pathlib.Path], formatFilenameWrite: Optional[str]) -> youOughtaKnow:
16
+ pythonSource = inspect.getsource(algorithmSource)
17
+ pythonSource = makeAstModuleForOneCallable(pythonSource, callableTarget, parametersNumba, inlineCallables, unpackArrays, allImports)
18
+ if not pythonSource: raise FREAKOUT
19
+ pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
20
+
21
+ if not relativePathWrite:
22
+ pathWrite = getPathSyntheticModules()
23
+ else:
24
+ pathWrite = getPathPackage() / relativePathWrite
25
+ if not formatFilenameWrite:
26
+ formatFilenameWrite = formatModuleNameDEFAULT + '.py'
27
+ pathFilename = pathWrite / formatFilenameWrite.format(callableTarget=callableTarget)
28
+
29
+ pathFilename.write_text(pythonSource)
30
+
31
+ howIsThisStillAThing = getPathPackage().parent
32
+ dumbassPythonNamespace = pathFilename.relative_to(howIsThisStillAThing).with_suffix('').parts
33
+ ImaModule = '.'.join(dumbassPythonNamespace)
34
+ astImportFrom = ast.ImportFrom(module=ImaModule, names=[ast.alias(name=callableTarget, asname=None)], level=0)
35
+
36
+ return youOughtaKnow(callableSynthesized=callableTarget, pathFilenameForMe=pathFilename, astForCompetentProgrammers=astImportFrom)
37
+
38
+ for callableTarget in listCallablesInline:
39
+ parametersNumba = None
40
+ inlineCallables = True
41
+ unpackArrays = False
42
+ allImports = None
43
+ match callableTarget:
44
+ case 'countParallel':
45
+ parametersNumba = parametersNumbaSuperJitParallel
46
+ case 'countSequential':
47
+ parametersNumba = parametersNumbaSuperJit
48
+ unpackArrays = True
49
+ case 'countInitialize':
50
+ parametersNumba = parametersNumbaDEFAULT
51
+ listStuffYouOughtaKnow.append(doThisStuff(callableTarget, parametersNumba, inlineCallables, unpackArrays, allImports, relativePathWrite, formatFilenameWrite))
52
+
53
+ if callableDispatcher:
54
+ callableTarget = getAlgorithmDispatcher().__name__
55
+ parametersNumba = None
56
+ inlineCallables = False
57
+ unpackArrays = False
58
+ allImports = UniversalImportTracker()
59
+ for stuff in listStuffYouOughtaKnow:
60
+ statement = stuff.astForCompetentProgrammers
61
+ if isinstance(statement, (ast.Import, ast.ImportFrom)):
62
+ allImports.addAst(statement)
63
+
64
+ listStuffYouOughtaKnow.append(doThisStuff(callableTarget, parametersNumba, inlineCallables, unpackArrays, allImports, relativePathWrite, formatFilenameWrite))
65
+
66
+ return listStuffYouOughtaKnow
67
+
68
+ if __name__ == '__main__':
69
+ setDatatypeModule('numpy', sourGrapes=True)
70
+ setDatatypeFoldsTotal('int64', sourGrapes=True)
71
+ setDatatypeElephino('uint8', sourGrapes=True)
72
+ setDatatypeLeavesTotal('uint8', sourGrapes=True)
73
+ Z0Z_setDatatypeModuleScalar('numba')
74
+ Z0Z_setDecoratorCallable('jit')
75
+ listCallablesInline: List[str] = ['countInitialize', 'countParallel', 'countSequential']
76
+ callableDispatcher = True
77
+ makeFlowNumbaOptimized(listCallablesInline, callableDispatcher)
File without changes
@@ -1,12 +1,12 @@
1
- from mapFolding import indexTrack
2
1
  from mapFolding import indexMy
3
- from numba import uint8
2
+ from mapFolding import indexTrack
4
3
  from numba import jit
4
+ from numba import uint8
5
5
  from numpy import ndarray
6
- from numpy import dtype
7
6
  from numpy import integer
8
- from typing import Any
7
+ from numpy import dtype
9
8
  from typing import Tuple
9
+ from typing import Any
10
10
 
11
11
  @jit((uint8[:, :, ::1], uint8[::1], uint8[::1], uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=True, inline='always', looplift=False, no_cfunc_wrapper=False, no_cpython_wrapper=False, nopython=True, parallel=False)
12
12
  def countInitialize(connectionGraph: ndarray[Tuple[int, int, int], dtype[integer[Any]]], gapsWhere: ndarray[Tuple[int], dtype[integer[Any]]], my: ndarray[Tuple[int], dtype[integer[Any]]], track: ndarray[Tuple[int, int], dtype[integer[Any]]]) -> None:
@@ -1,14 +1,14 @@
1
- from mapFolding import indexTrack
2
1
  from mapFolding import indexMy
3
- from numba import uint8
2
+ from mapFolding import indexTrack
4
3
  from numba import jit
4
+ from numba import uint8
5
5
  from numba import int64
6
6
  from numba import prange
7
7
  from numpy import ndarray
8
- from numpy import dtype
9
8
  from numpy import integer
10
- from typing import Any
9
+ from numpy import dtype
11
10
  from typing import Tuple
11
+ from typing import Any
12
12
 
13
13
  @jit((uint8[:, :, ::1], int64[::1], uint8[::1], uint8[::1], uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=True, inline='always', looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nopython=True, parallel=True)
14
14
  def countParallel(connectionGraph: ndarray[Tuple[int, int, int], dtype[integer[Any]]], foldGroups: ndarray[Tuple[int], dtype[integer[Any]]], gapsWhere: ndarray[Tuple[int], dtype[integer[Any]]], my: ndarray[Tuple[int], dtype[integer[Any]]], track: ndarray[Tuple[int, int], dtype[integer[Any]]]) -> None:
@@ -1,13 +1,13 @@
1
- from mapFolding import indexTrack
2
1
  from mapFolding import indexMy
3
- from numba import uint8
2
+ from mapFolding import indexTrack
4
3
  from numba import jit
4
+ from numba import uint8
5
5
  from numba import int64
6
6
  from numpy import ndarray
7
- from numpy import dtype
8
7
  from numpy import integer
9
- from typing import Any
8
+ from numpy import dtype
10
9
  from typing import Tuple
10
+ from typing import Any
11
11
 
12
12
  @jit((uint8[:, :, ::1], int64[::1], uint8[::1], uint8[::1], uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=True, inline='always', looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nopython=True, parallel=False)
13
13
  def countSequential(connectionGraph: ndarray[Tuple[int, int, int], dtype[integer[Any]]], foldGroups: ndarray[Tuple[int], dtype[integer[Any]]], gapsWhere: ndarray[Tuple[int], dtype[integer[Any]]], my: ndarray[Tuple[int], dtype[integer[Any]]], track: ndarray[Tuple[int, int], dtype[integer[Any]]]) -> None:
@@ -1,17 +1,17 @@
1
+ from mapFolding.syntheticModules.numba_countInitialize import countInitialize
2
+ from mapFolding.syntheticModules.numba_countParallel import countParallel
3
+ from mapFolding.syntheticModules.numba_countSequential import countSequential
1
4
  from mapFolding import indexMy
2
- from numba import uint8
3
5
  from numba import jit
6
+ from numba import uint8
4
7
  from numba import int64
5
8
  from numpy import ndarray
6
- from numpy import dtype
7
9
  from numpy import integer
8
- from typing import Any
10
+ from numpy import dtype
9
11
  from typing import Tuple
10
- from mapFolding.syntheticModules.numba_countInitialize import countInitialize
11
- from mapFolding.syntheticModules.numba_countParallel import countParallel
12
- from mapFolding.syntheticModules.numba_countSequential import countSequential
12
+ from typing import Any
13
13
 
14
- @jit((uint8[:, :, ::1], int64[::1], uint8[::1], uint8[::1], uint8[::1], uint8[:, ::1]), _nrt=True, boundscheck=True, cache=True, error_model='python', fastmath=False, forceinline=True, inline='always', looplift=False, no_cfunc_wrapper=False, no_cpython_wrapper=False, nopython=True, parallel=False)
14
+ @jit((uint8[:, :, ::1], int64[::1], uint8[::1], uint8[::1], uint8[::1], uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=True, inline='always', looplift=False, no_cfunc_wrapper=False, no_cpython_wrapper=False, nopython=True, parallel=False)
15
15
  def doTheNeedful(connectionGraph: ndarray[Tuple[int, int, int], dtype[integer[Any]]], foldGroups: ndarray[Tuple[int], dtype[integer[Any]]], gapsWhere: ndarray[Tuple[int], dtype[integer[Any]]], mapShape: ndarray[Tuple[int], dtype[integer[Any]]], my: ndarray[Tuple[int], dtype[integer[Any]]], track: ndarray[Tuple[int, int], dtype[integer[Any]]]) -> None:
16
16
  countInitialize(connectionGraph, gapsWhere, my, track)
17
17
  if my[indexMy.taskDivisions.value] > 0: