mapFolding 0.3.9__py3-none-any.whl → 0.3.10__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.
@@ -0,0 +1,383 @@
1
+ """
2
+ It's rough, but it works. Actually, the modules it produces are lightening fast.
3
+
4
+ Specific issues:
5
+ - When trying to run the synthesized file, `ModuleNotFoundError: No module named '<dynamic>'` unless I first re-save the file in the IDE.
6
+ - ast interprets the signature as `def countSequential() -> None:` even though there is a return statement.
7
+ - Similarly, but possibly my fault, `decorateCallableWithNumba` doesn't add the return type to the signature.
8
+
9
+ General issues:
10
+ - an ironic dearth of abstract functionality in this module based on ast.
11
+ - I don't have much experience with ast.
12
+ - ast is one of the few cases that absolutely benefits from an OOP paradigm, and I am comically inept at OOP.
13
+ - (almost) Everything prefixed with `Z0Z_` is something I want to substantially improve.
14
+ - convergence with other synthesize modules and functions would be good.
15
+ - while management of datatypes seems to be pretty good, managing pathFilenames could be better.
16
+ - as of this writing, there are zero direct tests for `someAssemblyRequired`.
17
+ """
18
+ from mapFolding import indexMy, indexTrack, ParametersNumba, parametersNumbaDEFAULT, getFilenameFoldsTotal, getPathJobRootDEFAULT, getPathFilenameFoldsTotal
19
+ from mapFolding import setDatatypeElephino, setDatatypeFoldsTotal, setDatatypeLeavesTotal, setDatatypeModule, hackSSOTdatatype, computationState
20
+ from mapFolding.someAssemblyRequired import makeStateJob, decorateCallableWithNumba, Z0Z_UnhandledDecorators
21
+ from typing import Optional, Callable, List, Sequence, cast, Dict, Set, Any, Union
22
+ from Z0Z_tools import updateExtendPolishDictionaryLists
23
+ import ast
24
+ import collections
25
+ import importlib
26
+ import importlib.util
27
+ import inspect
28
+ import more_itertools
29
+ import numpy
30
+ import os
31
+ import pathlib
32
+ import python_minifier
33
+
34
+ dictionaryImportFrom: Dict[str, List[str]] = collections.defaultdict(list)
35
+ datatypeModuleScalar = 'numba'
36
+
37
+ def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: Optional[str]=None) -> str:
38
+ """Converts a NumPy array into a compressed string representation using run-length encoding (RLE).
39
+
40
+ This function takes a NumPy array and converts it into an optimized string representation by:
41
+ 1. Compressing consecutive sequences of numbers into range objects
42
+ 2. Minimizing repeated zeros using array multiplication syntax
43
+ 3. Converting the result into a valid Python array initialization statement
44
+
45
+ Parameters:
46
+ arrayTarget (numpy.ndarray): The input NumPy array to be converted
47
+ identifierName (str): The variable name to use in the output string
48
+
49
+ Returns:
50
+ str: A string containing Python code that recreates the input array in compressed form.
51
+ Format: "{identifierName} = numpy.array({compressed_data}, dtype=numpy.{dtype})"
52
+
53
+ Example:
54
+ >>> arr = numpy.array([[0,0,0,1,2,3,4,0,0]])
55
+ >>> print(makeStrRLEcompacted(arr, "myArray"))
56
+ "myArray = numpy.array([[0]*3,*range(1,5),[0]*2], dtype=numpy.int64)"
57
+ """
58
+
59
+ def compressRangesNDArrayNoFlatten(arraySlice):
60
+ if isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim > 1:
61
+ return [compressRangesNDArrayNoFlatten(arraySlice[index]) for index in range(arraySlice.shape[0])]
62
+ elif isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim == 1:
63
+ listWithRanges = []
64
+ for group in more_itertools.consecutive_groups(arraySlice.tolist()):
65
+ ImaSerious = list(group)
66
+ if len(ImaSerious) <= 4:
67
+ listWithRanges += ImaSerious
68
+ else:
69
+ ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
70
+ listWithRanges += ImaRange
71
+ return listWithRanges
72
+ return arraySlice
73
+
74
+ arrayAsNestedLists = compressRangesNDArrayNoFlatten(arrayTarget)
75
+
76
+ stringMinimized = python_minifier.minify(str(arrayAsNestedLists))
77
+ commaZeroMaximum = arrayTarget.shape[-1] - 1
78
+ stringMinimized = stringMinimized.replace('[0' + ',0'*commaZeroMaximum + ']', '[0]*'+str(commaZeroMaximum+1))
79
+ for countZeros in range(commaZeroMaximum, 2, -1):
80
+ stringMinimized = stringMinimized.replace(',0'*countZeros + ']', ']+[0]*'+str(countZeros))
81
+
82
+ stringMinimized = stringMinimized.replace('range', '*range')
83
+
84
+ if identifierName:
85
+ return f"{identifierName} = array({stringMinimized}, dtype={arrayTarget.dtype})"
86
+ return stringMinimized
87
+
88
+ def makeImports() -> List[List[ast.ImportFrom]]:
89
+ global dictionaryImportFrom
90
+ dictionaryImportFrom = updateExtendPolishDictionaryLists(dictionaryImportFrom, destroyDuplicates=True)
91
+
92
+ def parseAlias(aliasString: str):
93
+ parts = aliasString.split(" as ")
94
+ if len(parts) == 2:
95
+ return ast.alias(name=parts[0].strip(), asname=parts[1].strip())
96
+ return ast.alias(name=aliasString.strip(), asname=None)
97
+
98
+ importStatements = [[
99
+ ast.ImportFrom(module=module, names=[ast.alias(name=identifierName, asname=None)
100
+ for identifierName in listIdentifiers], level=0)
101
+ for module, listIdentifiers in dictionaryImportFrom.items()]
102
+ ]
103
+
104
+ return importStatements
105
+
106
+ def evaluateArrayIn_body(node: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray) -> ast.FunctionDef:
107
+ global dictionaryImportFrom
108
+ arrayType = type(argData)
109
+ moduleConstructor = arrayType.__module__
110
+ constructorName = arrayType.__name__
111
+ # NOTE hack
112
+ constructorName = constructorName.replace('ndarray', 'array')
113
+ dictionaryImportFrom[moduleConstructor].append(constructorName)
114
+
115
+ for stmt in node.body.copy():
116
+ if isinstance(stmt, ast.Assign):
117
+ if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
118
+ astAssignee: ast.Name = stmt.targets[0]
119
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
120
+ dictionaryImportFrom[moduleConstructor].append(argData_dtypeName)
121
+ astSubscript: ast.Subscript = stmt.value
122
+ if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
123
+ indexAs_astAttribute: ast.Attribute = astSubscript.slice
124
+ indexAsStr = ast.unparse(indexAs_astAttribute)
125
+ argDataSlice = argData[eval(indexAsStr)]
126
+
127
+ onlyDataRLE = makeStrRLEcompacted(argDataSlice)
128
+ astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
129
+ dataAst = astStatement.value
130
+
131
+ arrayCall = ast.Call(
132
+ func=ast.Name(id=constructorName, ctx=ast.Load()) , args=[dataAst]
133
+ , keywords=[ast.keyword(arg='dtype', value=ast.Name(id=argData_dtypeName, ctx=ast.Load()) ) ] )
134
+
135
+ assignment = ast.Assign( targets=[astAssignee], value=arrayCall )
136
+ node.body.insert(0, assignment)
137
+ node.body.remove(stmt)
138
+
139
+ node.args.args.remove(astArg)
140
+ return node
141
+
142
+ def evaluate_argIn_body(node: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, Z0Z_listChaff: List[str]) -> ast.FunctionDef:
143
+ global dictionaryImportFrom
144
+ moduleConstructor = datatypeModuleScalar
145
+ for stmt in node.body.copy():
146
+ if isinstance(stmt, ast.Assign):
147
+ if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
148
+ astAssignee: ast.Name = stmt.targets[0]
149
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
150
+ dictionaryImportFrom[moduleConstructor].append(argData_dtypeName)
151
+ astSubscript: ast.Subscript = stmt.value
152
+ if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
153
+ indexAs_astAttribute: ast.Attribute = astSubscript.slice
154
+ indexAsStr = ast.unparse(indexAs_astAttribute)
155
+ argDataSlice: int = argData[eval(indexAsStr)].item()
156
+ astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[ast.Constant(value=argDataSlice)], keywords=[])
157
+ assignment = ast.Assign(targets=[astAssignee], value=astCall)
158
+ if astAssignee.id not in Z0Z_listChaff:
159
+ node.body.insert(0, assignment)
160
+ node.body.remove(stmt)
161
+ node.args.args.remove(astArg)
162
+ return node
163
+
164
+ def evaluateAnnAssignIn_body(node: ast.FunctionDef) -> ast.FunctionDef:
165
+ global dictionaryImportFrom
166
+ moduleConstructor = datatypeModuleScalar
167
+ for stmt in node.body.copy():
168
+ if isinstance(stmt, ast.AnnAssign):
169
+ if isinstance(stmt.target, ast.Name) and isinstance(stmt.value, ast.Constant):
170
+ astAssignee: ast.Name = stmt.target
171
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
172
+ dictionaryImportFrom[moduleConstructor].append(argData_dtypeName)
173
+ astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[stmt.value], keywords=[])
174
+ assignment = ast.Assign(targets=[astAssignee], value=astCall)
175
+ node.body.insert(0, assignment)
176
+ node.body.remove(stmt)
177
+ return node
178
+
179
+ def move_argTo_body(node: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray) -> ast.FunctionDef:
180
+ arrayType = type(argData)
181
+ moduleConstructor = arrayType.__module__
182
+ constructorName = arrayType.__name__
183
+ # NOTE hack
184
+ constructorName = constructorName.replace('ndarray', 'array')
185
+ argData_dtype: numpy.dtype = argData.dtype
186
+ argData_dtypeName = argData.dtype.name
187
+
188
+ global dictionaryImportFrom
189
+ dictionaryImportFrom[moduleConstructor].append(constructorName)
190
+ dictionaryImportFrom[moduleConstructor].append(argData_dtypeName)
191
+
192
+ onlyDataRLE = makeStrRLEcompacted(argData)
193
+ astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
194
+ dataAst = astStatement.value
195
+
196
+ arrayCall = ast.Call(
197
+ func=ast.Name(id=constructorName, ctx=ast.Load())
198
+ , args=[dataAst]
199
+ , keywords=[ast.keyword(arg='dtype' , value=ast.Name(id=argData_dtypeName , ctx=ast.Load()) ) ] )
200
+
201
+ assignment = ast.Assign( targets=[ast.Name(id=astArg.arg, ctx=ast.Store())], value=arrayCall )
202
+ node.body.insert(0, assignment)
203
+ node.args.args.remove(astArg)
204
+
205
+ return node
206
+
207
+ def makeDecorator(FunctionDefTarget: ast.FunctionDef, parametersNumba: Optional[ParametersNumba]=None) -> ast.FunctionDef:
208
+ if parametersNumba is None:
209
+ parametersNumbaExtracted: Dict[str, Any] = {}
210
+ for decoratorItem in FunctionDefTarget.decorator_list.copy():
211
+ if isinstance(decoratorItem, ast.Call) and isinstance(decoratorItem.func, ast.Attribute):
212
+ if getattr(decoratorItem.func.value, "id", None) == "numba" and decoratorItem.func.attr == "jit":
213
+ FunctionDefTarget.decorator_list.remove(decoratorItem)
214
+ for keywordItem in decoratorItem.keywords:
215
+ if isinstance(keywordItem.value, ast.Constant) and keywordItem.arg is not None:
216
+ parametersNumbaExtracted[keywordItem.arg] = keywordItem.value.value
217
+ if parametersNumbaExtracted:
218
+ parametersNumba = ParametersNumba(parametersNumbaExtracted) # type: ignore
219
+ else:
220
+ # TODO code duplication
221
+ for decoratorItem in FunctionDefTarget.decorator_list.copy():
222
+ if isinstance(decoratorItem, ast.Call) and isinstance(decoratorItem.func, ast.Attribute):
223
+ if getattr(decoratorItem.func.value, "id", None) == "numba" and decoratorItem.func.attr == "jit":
224
+ FunctionDefTarget.decorator_list.remove(decoratorItem)
225
+ FunctionDefTarget = Z0Z_UnhandledDecorators(FunctionDefTarget)
226
+ global dictionaryImportFrom
227
+ dictionaryImportFrom['numba'].append('jit')
228
+ FunctionDefTarget = decorateCallableWithNumba(FunctionDefTarget, parametersNumba)
229
+ # make sure the decorator is rendered as `@jit` and not `@numba.jit`
230
+ for decoratorItem in FunctionDefTarget.decorator_list:
231
+ if isinstance(decoratorItem, ast.Call) and isinstance(decoratorItem.func, ast.Attribute) and decoratorItem.func.attr == "jit":
232
+ decoratorItem.func = ast.Name(id="jit", ctx=ast.Load())
233
+ return FunctionDefTarget
234
+
235
+ def makeLauncher(identifierCallable: str) -> ast.Module:
236
+ linesLaunch = f"""
237
+ if __name__ == '__main__':
238
+ import time
239
+ timeStart = time.perf_counter()
240
+ {identifierCallable}()
241
+ print(time.perf_counter() - timeStart)
242
+ """
243
+ astLaunch = ast.parse(linesLaunch)
244
+ return astLaunch
245
+
246
+ def make_writeFoldsTotal(stateJob: computationState, pathFilenameFoldsTotal: pathlib.Path) -> ast.Module:
247
+ global dictionaryImportFrom
248
+ dictionaryImportFrom['numba'].append("objmode")
249
+ linesWriteFoldsTotal = f"""
250
+ groupsOfFolds *= {str(stateJob['foldGroups'][-1])}
251
+ print(groupsOfFolds)
252
+ with objmode():
253
+ open('{pathFilenameFoldsTotal.as_posix()}', 'w').write(str(groupsOfFolds))
254
+ return groupsOfFolds
255
+ """
256
+ return ast.parse(linesWriteFoldsTotal)
257
+
258
+ def removeIdentifierFrom_body(node: ast.FunctionDef, astArg: ast.arg) -> ast.FunctionDef:
259
+ for stmt in node.body.copy():
260
+ if isinstance(stmt, ast.Assign):
261
+ if isinstance(stmt.targets[0], ast.Subscript) and isinstance(stmt.targets[0].value, ast.Name):
262
+ if stmt.targets[0].value.id == astArg.arg:
263
+ node.body.remove(stmt)
264
+ node.args.args.remove(astArg)
265
+ return node
266
+
267
+ def astObjectToAstConstant(astFunction: ast.FunctionDef, object: str, value: int) -> ast.FunctionDef:
268
+ """
269
+ Replaces nodes in astFunction matching the AST of the string `object`
270
+ with a constant node holding the provided value.
271
+ """
272
+ targetExpression = ast.parse(object, mode='eval').body
273
+ targetDump = ast.dump(targetExpression, annotate_fields=False)
274
+
275
+ class ReplaceObjectWithConstant(ast.NodeTransformer):
276
+ def __init__(self, targetDump: str, constantValue: int) -> None:
277
+ self.targetDump = targetDump
278
+ self.constantValue = constantValue
279
+
280
+ def generic_visit(self, node: ast.AST) -> ast.AST:
281
+ currentDump = ast.dump(node, annotate_fields=False)
282
+ if currentDump == self.targetDump:
283
+ return ast.copy_location(ast.Constant(value=self.constantValue), node)
284
+ return super().generic_visit(node)
285
+
286
+ transformer = ReplaceObjectWithConstant(targetDump, value)
287
+ newFunction = transformer.visit(astFunction)
288
+ ast.fix_missing_locations(newFunction)
289
+ return newFunction
290
+
291
+ def astNameToAstConstant(astFunction: ast.FunctionDef, name: str, value: int) -> ast.FunctionDef:
292
+ class ReplaceNameWithConstant(ast.NodeTransformer):
293
+ def visit_Name(self, node: ast.Name) -> ast.AST:
294
+ if node.id == name:
295
+ return ast.copy_location(ast.Constant(value=value), node)
296
+ return node
297
+ return ReplaceNameWithConstant().visit(astFunction)
298
+
299
+ def writeJobNumba(listDimensions: Sequence[int], callableSource: Callable, parametersNumba: Optional[ParametersNumba]=None, pathFilenameWriteJob: Optional[Union[str, os.PathLike[str]]] = None) -> pathlib.Path:
300
+ stateJob = makeStateJob(listDimensions, writeJob=False)
301
+ codeSource = inspect.getsource(callableSource)
302
+ astSource = ast.parse(codeSource)
303
+
304
+ pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateJob['mapShape'])
305
+
306
+ FunctionDefTarget = next((node for node in astSource.body if isinstance(node, ast.FunctionDef) and node.name == callableSource.__name__), None)
307
+
308
+ if not FunctionDefTarget:
309
+ raise ValueError(f"Could not find function {callableSource.__name__} in source code")
310
+
311
+ Z0Z_listArgsTarget = ['connectionGraph', 'gapsWhere']
312
+ Z0Z_listArraysEvaluate = ['track']
313
+ Z0Z_listArgsEvaluate = ['my']
314
+ Z0Z_listChaff = ['taskIndex', 'dimensionsTotal']
315
+ Z0Z_listArgsRemove = ['foldGroups']
316
+ for astArgument in FunctionDefTarget.args.args.copy():
317
+ if astArgument.arg in Z0Z_listArgsTarget:
318
+ FunctionDefTarget = move_argTo_body(FunctionDefTarget, astArgument, stateJob[astArgument.arg])
319
+ elif astArgument.arg in Z0Z_listArraysEvaluate:
320
+ FunctionDefTarget = evaluateArrayIn_body(FunctionDefTarget, astArgument, stateJob[astArgument.arg])
321
+ elif astArgument.arg in Z0Z_listArgsEvaluate:
322
+ FunctionDefTarget = evaluate_argIn_body(FunctionDefTarget, astArgument, stateJob[astArgument.arg], Z0Z_listChaff)
323
+ elif astArgument.arg in Z0Z_listArgsRemove:
324
+ FunctionDefTarget = removeIdentifierFrom_body(FunctionDefTarget, astArgument)
325
+
326
+ FunctionDefTarget = evaluateAnnAssignIn_body(FunctionDefTarget)
327
+ FunctionDefTarget = astNameToAstConstant(FunctionDefTarget, 'dimensionsTotal', int(stateJob['my'][indexMy.dimensionsTotal]))
328
+ FunctionDefTarget = astObjectToAstConstant(FunctionDefTarget, 'foldGroups[-1]', int(stateJob['foldGroups'][-1]))
329
+
330
+ global identifierCallableLaunch
331
+ identifierCallableLaunch = FunctionDefTarget.name
332
+
333
+ FunctionDefTarget = makeDecorator(FunctionDefTarget, parametersNumba)
334
+
335
+ astWriteFoldsTotal = make_writeFoldsTotal(stateJob, pathFilenameFoldsTotal)
336
+ FunctionDefTarget.body += astWriteFoldsTotal.body
337
+
338
+ astLauncher = makeLauncher(FunctionDefTarget.name)
339
+
340
+ astImports = makeImports()
341
+
342
+ astModule = ast.Module(
343
+ body=cast(List[ast.stmt]
344
+ , astImports
345
+ + [FunctionDefTarget]
346
+ + [astLauncher])
347
+ , type_ignores=[]
348
+ )
349
+ ast.fix_missing_locations(astModule)
350
+
351
+ codeSource = ast.unparse(astModule)
352
+
353
+ if pathFilenameWriteJob is None:
354
+ filename = getFilenameFoldsTotal(stateJob['mapShape'])
355
+ pathRoot = getPathJobRootDEFAULT()
356
+ pathFilenameWriteJob = pathlib.Path(pathRoot, pathlib.Path(filename).stem, pathlib.Path(filename).with_suffix('.py'))
357
+ else:
358
+ pathFilenameWriteJob = pathlib.Path(pathFilenameWriteJob)
359
+ pathFilenameWriteJob.parent.mkdir(parents=True, exist_ok=True)
360
+
361
+ pathFilenameWriteJob.write_text(codeSource)
362
+ return pathFilenameWriteJob
363
+
364
+ if __name__ == '__main__':
365
+ listDimensions = [2,15]
366
+ setDatatypeFoldsTotal('int64', sourGrapes=True)
367
+ setDatatypeElephino('uint8', sourGrapes=True)
368
+ setDatatypeLeavesTotal('uint8', sourGrapes=True)
369
+ from mapFolding.syntheticModules.numba_countSequential import countSequential
370
+ callableSource = countSequential
371
+ pathFilenameModule = writeJobNumba(listDimensions, callableSource, parametersNumbaDEFAULT)
372
+
373
+ # Induce numba.jit compilation
374
+ # TODO Inducing compilation might be causing the `ModuleNotFoundError: No module named '<dynamic>'` error
375
+
376
+ # moduleSpec = importlib.util.spec_from_file_location(pathFilenameModule.stem, pathFilenameModule)
377
+ # if moduleSpec is None: raise ImportError(f"Could not load module specification from {pathFilenameModule}")
378
+ # module = importlib.util.module_from_spec(moduleSpec)
379
+ # if moduleSpec.loader is None: raise ImportError(f"Could not load module from {moduleSpec}")
380
+ # moduleSpec.loader.exec_module(module)
381
+
382
+ # from mapFolding.someAssemblyRequired.getLLVMforNoReason import writeModuleLLVM
383
+ # pathFilenameLLVM = writeModuleLLVM(pathFilenameModule, identifierCallableLaunch)
@@ -1,4 +1,4 @@
1
- from mapFolding import getAlgorithmSource, relativePathSyntheticModules
1
+ from mapFolding import getAlgorithmSource, getPathSyntheticModules
2
2
  from mapFolding import setDatatypeModule, setDatatypeFoldsTotal, setDatatypeElephino, setDatatypeLeavesTotal
3
3
  from typing import Optional
4
4
  import ast
@@ -18,7 +18,7 @@ def writeJax(*, codeSource: Optional[str] = None, pathFilenameAlgorithm: Optiona
18
18
  else:
19
19
  raise NotImplementedError("You haven't written this part yet.")
20
20
  if pathFilenameDestination is None:
21
- pathFilenameDestination = pathFilenameAlgorithm.parent / relativePathSyntheticModules / "countJax.py"
21
+ pathFilenameDestination = getPathSyntheticModules() / "countJax.py"
22
22
  # pathFilenameDestination.write_text(transformedText)
23
23
 
24
24
  if __name__ == '__main__':
@@ -10,6 +10,9 @@ from mapFolding import (
10
10
  moduleOfSyntheticModules,
11
11
  myPackageNameIs,
12
12
  ParametersNumba,
13
+ parametersNumbaSuperJit,
14
+ parametersNumbaFailEarly,
15
+ parametersNumbaSuperJitParallel,
13
16
  parametersNumbaDEFAULT,
14
17
  setDatatypeElephino,
15
18
  setDatatypeFoldsTotal,
@@ -32,6 +35,14 @@ Convert types
32
35
  e.g. `groupsOfFolds: int = 0` to `groupsOfFolds = numba.types.{datatypeLarge}(0)`
33
36
  This isn't necessary for Numba, but I may the infrastructure for other compilers or paradigms."""
34
37
 
38
+ def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
39
+ # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
40
+ for decoratorItem in astCallable.decorator_list.copy():
41
+ import warnings
42
+ astCallable.decorator_list.remove(decoratorItem)
43
+ warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
44
+ return astCallable
45
+
35
46
  class RecursiveInliner(ast.NodeTransformer):
36
47
  """
37
48
  Class RecursiveInliner:
@@ -93,7 +104,7 @@ class RecursiveInliner(ast.NodeTransformer):
93
104
  return [self.visit(stmt) for stmt in inlineDefinition.body]
94
105
  return self.generic_visit(node)
95
106
 
96
- def decorateCallableWithNumba(astCallable: ast.FunctionDef, parallel: bool=False) -> ast.FunctionDef:
107
+ def decorateCallableWithNumba(astCallable: ast.FunctionDef, parametersNumba: Optional[ParametersNumba]=None) -> ast.FunctionDef:
97
108
  """
98
109
  Decorates an AST function definition with Numba JIT compilation parameters.
99
110
 
@@ -181,8 +192,7 @@ def decorateCallableWithNumba(astCallable: ast.FunctionDef, parallel: bool=False
181
192
 
182
193
  return ast.Subscript(value=datatypeNumba, slice=shapeAST, ctx=ast.Load())
183
194
 
184
- # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
185
- # callableSourceDecorators = [decorator for decorator in callableInlined.decorator_list]
195
+ astCallable = Z0Z_UnhandledDecorators(astCallable)
186
196
 
187
197
  listNumbaParameterSignature: Sequence[ast.expr] = []
188
198
  for parameter in astCallable.args.args:
@@ -190,15 +200,31 @@ def decorateCallableWithNumba(astCallable: ast.FunctionDef, parallel: bool=False
190
200
  if (signatureElement):
191
201
  listNumbaParameterSignature.append(signatureElement)
192
202
 
193
- astArgsNumbaSignature = ast.Tuple(elts=listNumbaParameterSignature, ctx=ast.Load())
194
-
195
- if astCallable.name == 'countInitialize' or astCallable.name == 'doTheNeedful':
196
- parametersNumba = {}
203
+ astTupleSignatureParameters = ast.Tuple(elts=listNumbaParameterSignature, ctx=ast.Load())
204
+
205
+ # TODO if `astCallable` has a return, the return needs to be added to `astArgsNumbaSignature` in the appropriate place
206
+ # The return, when placed in the args, is treated as a `Call`. This is logical because numba is converting to machine code.
207
+ # , args=[Call(func=Name(id='int64', ctx=Load()))]
208
+ ast_argsSignature = astTupleSignatureParameters
209
+
210
+ ImaReturn = next((node for node in astCallable.body if isinstance(node, ast.Return)), None)
211
+ # Return(value=Name(id='groupsOfFolds', ctx=Load()))]
212
+ if ImaReturn is not None and isinstance(ImaReturn.value, ast.Name):
213
+ my_idIf_I_wereA_astCall_func_astName_idParameter = ImaReturn.value.id
214
+ ast_argsSignature = ast.Call(
215
+ func=ast.Name(id=my_idIf_I_wereA_astCall_func_astName_idParameter, ctx=ast.Load()),
216
+ args=[astTupleSignatureParameters],
217
+ keywords=[]
218
+ )
197
219
  else:
198
- parametersNumba = parametersNumbaDEFAULT if not parallel else ParametersNumba({**parametersNumbaDEFAULT, 'parallel': True})
220
+ ast_argsSignature = astTupleSignatureParameters
221
+
222
+ if parametersNumba is None:
223
+ parametersNumba = parametersNumbaDEFAULT
224
+
199
225
  listKeywordsNumbaSignature = [ast.keyword(arg=parameterName, value=ast.Constant(value=parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
200
226
 
201
- astDecoratorNumba = ast.Call(func=ast.Attribute(value=ast.Name(id='numba', ctx=ast.Load()), attr='jit', ctx=ast.Load()), args=[astArgsNumbaSignature], keywords=listKeywordsNumbaSignature)
227
+ astDecoratorNumba = ast.Call(func=ast.Attribute(value=ast.Name(id='numba', ctx=ast.Load()), attr='jit', ctx=ast.Load()), args=[ast_argsSignature], keywords=listKeywordsNumbaSignature)
202
228
 
203
229
  astCallable.decorator_list = [astDecoratorNumba]
204
230
  return astCallable
@@ -311,6 +337,7 @@ class UnpackArrayAccesses(ast.NodeTransformer):
311
337
  return node
312
338
 
313
339
  def inlineOneCallable(codeSource: str, callableTarget: str):
340
+
314
341
  """
315
342
  Inlines a target callable function and its dependencies within the provided code source.
316
343
 
@@ -347,8 +374,17 @@ def inlineOneCallable(codeSource: str, callableTarget: str):
347
374
 
348
375
  if callableInlined:
349
376
  ast.fix_missing_locations(callableInlined)
350
- parallel = callableTarget == 'countParallel'
351
- callableDecorated = decorateCallableWithNumba(callableInlined, parallel)
377
+ parametersNumba = None
378
+
379
+ match callableTarget:
380
+ case 'countParallel':
381
+ parametersNumba = parametersNumbaSuperJitParallel
382
+ case 'countSequential':
383
+ parametersNumba = parametersNumbaSuperJit
384
+ case 'countInitialize':
385
+ parametersNumba = parametersNumbaDEFAULT
386
+
387
+ callableDecorated = decorateCallableWithNumba(callableInlined, parametersNumba)
352
388
 
353
389
  if callableTarget == 'countSequential':
354
390
  unpackerMy = UnpackArrayAccesses(indexMy, 'my')
@@ -365,59 +401,50 @@ def inlineOneCallable(codeSource: str, callableTarget: str):
365
401
  return moduleSource
366
402
 
367
403
  def makeDispatcherNumba(codeSource: str, callableTarget: str, listStuffYouOughtaKnow: List[youOughtaKnow]) -> str:
368
- """Creates AST for the dispatcher module that coordinates the optimized functions."""
369
- docstringDispatcherNumba = """
370
- What in tarnation is this stupid module and function?
371
-
372
- - This function is not in the same module as `countFolds` so that we can delay Numba just-in-time (jit) compilation of this function and the finalization of its settings until we are ready.
373
- - This function is not in the same module as the next function, which does the hard work, so that we can delay `numba.jit` compilation of the next function.
374
- - This function is "jitted" but the next function is super jitted, which makes it too arrogant to talk to plebian Python functions. It will, however, reluctantly talk to basic jitted functions.
375
- - So this module can talk to the next function, and because this module isn't as arrogant, it will talk to the low-class `countFolds` that called this function. Well, with a few restrictions, of course:
376
- - No `TypedDict`
377
- - The plebs must clean up their own memory problems
378
- - No oversized integers
379
- - No global variables, only global constants
380
- - It won't accept pleb nonlocal variables either
381
- - Python "class": they are all inferior to the jit class
382
- - No `**kwargs`
383
- - and just a few dozen-jillion other things.
384
- """
385
404
 
386
- # Parse source code
387
- sourceAST = ast.parse(codeSource)
405
+ docstringDispatcherNumba = """What in tarnation is this stupid module and function?
406
+
407
+ - This function is not in the same module as `countFolds` so that we can delay Numba just-in-time (jit) compilation of this function and the finalization of its settings until we are ready.
408
+ - This function is not in the same module as the next function, which does the hard work, so that we can delay `numba.jit` compilation of the next function.
409
+ - This function is "jitted" but the next function is super jitted, which makes it too arrogant to talk to plebian Python functions. It will, however, reluctantly talk to basic jitted functions.
410
+ - So this module can talk to the next function, and because this module isn't as arrogant, it will talk to the low-class `countFolds` that called this function. Well, with a few restrictions, of course:
411
+ - No `TypedDict`
412
+ - The plebs must clean up their own memory problems
413
+ - No oversized integers
414
+ - No global variables, only global constants
415
+ - It won't accept pleb nonlocal variables either
416
+ - Python "class": they are all inferior to the jit class
417
+ - No `**kwargs`
418
+ - and just a few dozen-jillion other things."""
388
419
 
389
- # Extract imports and target function definition
390
- importsAST = [node for node in sourceAST.body if isinstance(node, (ast.Import, ast.ImportFrom))]
391
- FunctionDefTarget = next((node for node in sourceAST.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
420
+ astSource = ast.parse(codeSource)
421
+
422
+ astImports = [node for node in astSource.body if isinstance(node, (ast.Import, ast.ImportFrom))]
423
+ FunctionDefTarget = next((node for node in astSource.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
392
424
 
393
425
  if not FunctionDefTarget:
394
426
  raise ValueError(f"Could not find function {callableTarget} in source code")
395
427
 
396
428
  # Zero-out the decorator list
397
- FunctionDefTarget.decorator_list=[]
398
- # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
399
- # FunctionDefTargetDecorators = [decorator for decorator in FunctionDefTarget.decorator_list]
429
+ FunctionDefTarget = Z0Z_UnhandledDecorators(FunctionDefTarget)
400
430
 
401
431
  # Add Numba decorator
402
- FunctionDefTarget = decorateCallableWithNumba(FunctionDefTarget, parallel=False)
432
+ FunctionDefTarget = decorateCallableWithNumba(FunctionDefTarget, parametersNumbaFailEarly)
403
433
  FunctionDefTarget.body.insert(0, ast.Expr(value=ast.Constant(value=docstringDispatcherNumba)))
404
434
 
405
- # Combine everything into a module
406
- moduleAST = ast.Module(
435
+ astModule = ast.Module(
407
436
  body=cast(List[ast.stmt]
408
- , importsAST
437
+ , astImports
409
438
  + [Don_Lapre_The_Road_to_Self_Improvement_For_Programmers_by_Using_Short_Identifiers.astForCompetentProgrammers
410
439
  for Don_Lapre_The_Road_to_Self_Improvement_For_Programmers_by_Using_Short_Identifiers in listStuffYouOughtaKnow]
411
440
  + [FunctionDefTarget])
412
441
  , type_ignores=[]
413
442
  )
414
443
 
415
- ast.fix_missing_locations(moduleAST)
416
- return ast.unparse(moduleAST)
444
+ ast.fix_missing_locations(astModule)
445
+ return ast.unparse(astModule)
417
446
 
418
447
  def makeNumbaOptimizedFlow(listCallablesInline: List[str], callableDispatcher: Optional[str] = None, algorithmSource: Optional[ModuleType] = None):
419
- """Synthesizes numba-optimized versions of map folding functions."""
420
-
421
448
  if not algorithmSource:
422
449
  algorithmSource = getAlgorithmSource()
423
450
 
@@ -1,12 +1,11 @@
1
- from numpy.typing import NDArray
1
+ from typing import Any, Tuple
2
2
  import numpy
3
- from numpy import integer
3
+ from numpy import dtype, integer, ndarray
4
4
  import numba
5
5
  from mapFolding import indexMy, indexTrack
6
- from typing import Any, Tuple
7
6
 
8
- @numba.jit((numba.uint8[:, :, ::1], numba.uint8[::1], numba.uint8[::1], numba.uint8[:, ::1]))
9
- def countInitialize(connectionGraph: numpy.ndarray[Tuple[int, int, int], numpy.dtype[integer[Any]]], gapsWhere: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], my: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], track: numpy.ndarray[Tuple[int, int], numpy.dtype[integer[Any]]]) -> None:
7
+ @numba.jit((numba.uint8[:, :, ::1], numba.uint8[::1], numba.uint8[::1], numba.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)
8
+ 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:
10
9
  while my[indexMy.leaf1ndex.value]:
11
10
  if my[indexMy.leaf1ndex.value] <= 1 or track[indexTrack.leafBelow.value, 0] == 1:
12
11
  my[indexMy.dimensionsUnconstrained.value] = my[indexMy.dimensionsTotal.value]
@@ -1,12 +1,11 @@
1
- from numpy.typing import NDArray
2
- import numpy
3
- from numpy import integer
4
- import numba
5
1
  from mapFolding import indexMy, indexTrack
6
2
  from typing import Any, Tuple
3
+ import numpy
4
+ from numpy import dtype, integer, ndarray
5
+ import numba
7
6
 
8
- @numba.jit((numba.uint8[:, :, ::1], numba.int64[::1], numba.uint8[::1], numba.uint8[::1], numba.uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=False, inline='never', looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nopython=True, parallel=True)
9
- def countParallel(connectionGraph: numpy.ndarray[Tuple[int, int, int], numpy.dtype[integer[Any]]], foldGroups: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], gapsWhere: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], my: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], track: numpy.ndarray[Tuple[int, int], numpy.dtype[integer[Any]]]) -> None:
7
+ @numba.jit((numba.uint8[:, :, ::1], numba.int64[::1], numba.uint8[::1], numba.uint8[::1], numba.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)
8
+ 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:
10
9
  gapsWherePARALLEL = gapsWhere.copy()
11
10
  myPARALLEL = my.copy()
12
11
  trackPARALLEL = track.copy()
@@ -1,12 +1,11 @@
1
- from numpy.typing import NDArray
2
1
  import numpy
3
- from numpy import integer
2
+ from numpy import dtype, integer, ndarray
4
3
  import numba
5
4
  from mapFolding import indexMy, indexTrack
6
5
  from typing import Any, Tuple
7
6
 
8
- @numba.jit((numba.uint8[:, :, ::1], numba.int64[::1], numba.uint8[::1], numba.uint8[::1], numba.uint8[:, ::1]), _nrt=True, boundscheck=False, cache=True, error_model='numpy', fastmath=True, forceinline=False, inline='never', looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nopython=True, parallel=False)
9
- def countSequential(connectionGraph: numpy.ndarray[Tuple[int, int, int], numpy.dtype[integer[Any]]], foldGroups: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], gapsWhere: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], my: numpy.ndarray[Tuple[int], numpy.dtype[integer[Any]]], track: numpy.ndarray[Tuple[int, int], numpy.dtype[integer[Any]]]) -> None:
7
+ @numba.jit((numba.uint8[:, :, ::1], numba.int64[::1], numba.uint8[::1], numba.uint8[::1], numba.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)
8
+ 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:
10
9
  leafBelow = track[indexTrack.leafBelow.value]
11
10
  gapRangeStart = track[indexTrack.gapRangeStart.value]
12
11
  countDimensionsGapped = track[indexTrack.countDimensionsGapped.value]