mapFolding 0.3.12__py3-none-any.whl → 0.4.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.
Files changed (45) 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 +2 -1
  15. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +12 -12
  16. mapFolding/someAssemblyRequired/makeJob.py +48 -48
  17. mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +17 -17
  18. mapFolding/someAssemblyRequired/synthesizeNumba.py +343 -633
  19. mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +371 -0
  20. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +150 -0
  21. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +75 -0
  22. mapFolding/syntheticModules/__init__.py +0 -0
  23. mapFolding/syntheticModules/numba_countInitialize.py +3 -3
  24. mapFolding/syntheticModules/numba_countParallel.py +3 -3
  25. mapFolding/syntheticModules/numba_countSequential.py +3 -3
  26. mapFolding/syntheticModules/numba_doTheNeedful.py +6 -6
  27. mapFolding/theDao.py +165 -165
  28. mapFolding/theSSOT.py +176 -172
  29. mapFolding/theSSOTnumba.py +90 -74
  30. mapFolding-0.4.0.dist-info/METADATA +122 -0
  31. mapFolding-0.4.0.dist-info/RECORD +41 -0
  32. tests/conftest.py +238 -128
  33. tests/test_oeis.py +80 -80
  34. tests/test_other.py +137 -224
  35. tests/test_tasks.py +21 -21
  36. tests/test_types.py +2 -2
  37. mapFolding/someAssemblyRequired/synthesizeNumbaHardcoding.py +0 -188
  38. mapFolding-0.3.12.dist-info/METADATA +0 -155
  39. mapFolding-0.3.12.dist-info/RECORD +0 -40
  40. tests/conftest_tmpRegistry.py +0 -62
  41. tests/conftest_uniformTests.py +0 -53
  42. {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/LICENSE +0 -0
  43. {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/WHEEL +0 -0
  44. {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/entry_points.txt +0 -0
  45. {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,637 +1,347 @@
1
1
  """I think this module is free of hardcoded values.
2
2
  TODO: consolidate the logic in this module."""
3
- from mapFolding import (
4
- computationState,
5
- EnumIndices,
6
- FREAKOUT,
7
- getAlgorithmSource,
8
- getFilenameFoldsTotal,
9
- getPathFilenameFoldsTotal,
10
- getPathJobRootDEFAULT,
11
- getPathSyntheticModules,
12
- hackSSOTdatatype,
13
- indexMy,
14
- indexTrack,
15
- moduleOfSyntheticModules,
16
- myPackageNameIs,
17
- ParametersNumba,
18
- parametersNumbaDEFAULT,
19
- parametersNumbaFailEarly,
20
- parametersNumbaSuperJit,
21
- parametersNumbaSuperJitParallel,
22
- setDatatypeElephino,
23
- setDatatypeFoldsTotal,
24
- setDatatypeLeavesTotal,
25
- setDatatypeModule,
26
- Z0Z_getDatatypeModuleScalar,
27
- Z0Z_getDecoratorCallable,
28
- Z0Z_identifierCountFolds,
29
- Z0Z_setDatatypeModuleScalar,
30
- Z0Z_setDecoratorCallable,
31
- )
32
- from mapFolding.someAssemblyRequired import makeStateJob
33
- from types import ModuleType
34
- from typing import Any, Callable, cast, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
35
- import ast
36
- import autoflake
37
- import collections
38
- import inspect
39
- import more_itertools
40
- import numba
41
- import numpy
42
- import os
43
- import pathlib
44
- import python_minifier
45
-
46
- youOughtaKnow = collections.namedtuple('youOughtaKnow', ['callableSynthesized', 'pathFilenameForMe', 'astForCompetentProgrammers'])
47
-
48
- class UniversalImportTracker:
49
- def __init__(self):
50
- self.dictionaryImportFrom = collections.defaultdict(set)
51
- self.setImport = set()
52
-
53
- def addAst(self, astImport_: Union[ast.Import, ast.ImportFrom]) -> None:
54
- if isinstance(astImport_, ast.Import):
55
- for alias in astImport_.names:
56
- self.setImport.add(alias.name)
57
- elif isinstance(astImport_, ast.ImportFrom):
58
- self.dictionaryImportFrom[astImport_.module].update(alias.name for alias in astImport_.names)
59
-
60
- def addImportFromStr(self, module: str, name: str) -> None:
61
- self.dictionaryImportFrom[module].add(name)
62
-
63
- def addImportStr(self, name: str) -> None:
64
- self.setImport.add(name)
65
-
66
- def makeListAst(self) -> List[Union[ast.ImportFrom, ast.Import]]:
67
- 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]
68
- listAstImport = [ast.Import(names=[ast.alias(name=name, asname=None)]) for name in self.setImport]
69
- return listAstImportFrom + listAstImport
70
-
71
- class NodeReplacer(ast.NodeTransformer):
72
- """Generic node replacement using configurable predicate and builder."""
73
- def __init__(self, findMe: Callable[[ast.AST], bool], nodeReplacementBuilder: Callable[[ast.AST], ast.AST]):
74
- self.findMe = findMe
75
- self.nodeReplacementBuilder = nodeReplacementBuilder
76
-
77
- def visit(self, node: ast.AST) -> ast.AST:
78
- if self.findMe(node):
79
- return self.nodeReplacementBuilder(node)
80
- return super().visit(node)
81
-
82
- class RecursiveInliner(ast.NodeTransformer):
83
- """
84
- Class RecursiveInliner:
85
- A custom AST NodeTransformer designed to recursively inline function calls from a given dictionary
86
- of function definitions into the AST. Once a particular function has been inlined, it is marked
87
- as completed to avoid repeated inlining. This transformation modifies the AST in-place by substituting
88
- eligible function calls with the body of their corresponding function.
89
- Attributes:
90
- dictionaryFunctions (Dict[str, ast.FunctionDef]):
91
- A mapping of function name to its AST definition, used as a source for inlining.
92
- callablesCompleted (Set[str]):
93
- A set to track function names that have already been inlined to prevent multiple expansions.
94
- Methods:
95
- inlineFunctionBody(callableTargetName: str) -> Optional[ast.FunctionDef]:
96
- Retrieves the AST definition for a given function name from dictionaryFunctions
97
- and recursively inlines any function calls within it. Returns the function definition
98
- that was inlined or None if the function was already processed.
99
- visit_Call(callNode: ast.Call) -> ast.AST:
100
- Inspects calls within the AST. If a function call matches one in dictionaryFunctions,
101
- it is replaced by the inlined body. If the last statement in the inlined body is a return
102
- or an expression, that value or expression is substituted; otherwise, a constant is returned.
103
- visit_Expr(node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
104
- Handles expression nodes in the AST. If the expression is a function call from
105
- dictionaryFunctions, its statements are expanded in place, effectively inlining
106
- the called function's statements into the surrounding context.
107
- """
108
- def __init__(self, dictionaryFunctions: Dict[str, ast.FunctionDef]):
109
- self.dictionaryFunctions = dictionaryFunctions
110
- self.callablesCompleted: Set[str] = set()
111
-
112
- def inlineFunctionBody(self, callableTargetName: str) -> Optional[ast.FunctionDef]:
113
- if (callableTargetName in self.callablesCompleted):
114
- return None
115
-
116
- self.callablesCompleted.add(callableTargetName)
117
- inlineDefinition = self.dictionaryFunctions[callableTargetName]
118
- for astNode in ast.walk(inlineDefinition):
119
- self.visit(astNode)
120
- return inlineDefinition
121
-
122
- def visit_Call(self, node: ast.Call) -> ast.AST:
123
- callNodeVisited = self.generic_visit(node)
124
- if (isinstance(callNodeVisited, ast.Call) and isinstance(callNodeVisited.func, ast.Name) and callNodeVisited.func.id in self.dictionaryFunctions):
125
- inlineDefinition = self.inlineFunctionBody(callNodeVisited.func.id)
126
- if (inlineDefinition and inlineDefinition.body):
127
- statementTerminating = inlineDefinition.body[-1]
128
- if (isinstance(statementTerminating, ast.Return) and statementTerminating.value is not None):
129
- return self.visit(statementTerminating.value)
130
- elif (isinstance(statementTerminating, ast.Expr) and statementTerminating.value is not None):
131
- return self.visit(statementTerminating.value)
132
- return ast.Constant(value=None)
133
- return callNodeVisited
134
-
135
- def visit_Expr(self, node: ast.Expr) -> Union[ast.AST, List[ast.AST]]:
136
- if (isinstance(node.value, ast.Call)):
137
- if (isinstance(node.value.func, ast.Name) and node.value.func.id in self.dictionaryFunctions):
138
- inlineDefinition = self.inlineFunctionBody(node.value.func.id)
139
- if (inlineDefinition):
140
- return [self.visit(stmt) for stmt in inlineDefinition.body]
141
- return self.generic_visit(node)
142
-
143
- class UnpackArrays(ast.NodeTransformer):
144
- """
145
- A class that transforms array accesses using enum indices into local variables.
146
-
147
- This AST transformer identifies array accesses using enum indices and replaces them
148
- with local variables, adding initialization statements at the start of functions.
149
-
150
- Parameters:
151
- enumIndexClass (Type[EnumIndices]): The enum class used for array indexing
152
- arrayName (str): The name of the array being accessed
153
-
154
- Attributes:
155
- enumIndexClass (Type[EnumIndices]): Stored enum class for index lookups
156
- arrayName (str): Name of the array being transformed
157
- substitutions (dict): Tracks variable substitutions and their original nodes
158
-
159
- The transformer handles two main cases:
160
- 1. Scalar array access - array[EnumIndices.MEMBER]
161
- 2. Array slice access - array[EnumIndices.MEMBER, other_indices...]
162
- For each identified access pattern, it:
163
- 1. Creates a local variable named after the enum member
164
- 2. Adds initialization code at function start
165
- 3. Replaces original array access with the local variable
166
- """
167
-
168
- def __init__(self, enumIndexClass: Type[EnumIndices], arrayName: str):
169
- self.enumIndexClass = enumIndexClass
170
- self.arrayName = arrayName
171
- self.substitutions = {}
172
-
173
- def extract_member_name(self, node: ast.AST) -> Optional[str]:
174
- """Recursively extract enum member name from any node in the AST."""
175
- if isinstance(node, ast.Attribute) and node.attr == 'value':
176
- innerAttribute = node.value
177
- while isinstance(innerAttribute, ast.Attribute):
178
- if (isinstance(innerAttribute.value, ast.Name) and innerAttribute.value.id == self.enumIndexClass.__name__):
179
- return innerAttribute.attr
180
- innerAttribute = innerAttribute.value
181
- return None
182
-
183
- def transform_slice_element(self, node: ast.AST) -> ast.AST:
184
- """Transform any enum references within a slice element."""
185
- if isinstance(node, ast.Subscript):
186
- if isinstance(node.slice, ast.Attribute):
187
- member_name = self.extract_member_name(node.slice)
188
- if member_name:
189
- return ast.Name(id=member_name, ctx=node.ctx)
190
- elif isinstance(node, ast.Tuple):
191
- # Handle tuple slices by transforming each element
192
- return ast.Tuple(elts=cast(List[ast.expr], [self.transform_slice_element(elt) for elt in node.elts]), ctx=node.ctx)
193
- elif isinstance(node, ast.Attribute):
194
- member_name = self.extract_member_name(node)
195
- if member_name:
196
- return ast.Name(id=member_name, ctx=ast.Load())
197
- return node
198
-
199
- def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
200
- # Recursively visit any nested subscripts in value or slice
201
- node.value = self.visit(node.value)
202
- node.slice = self.visit(node.slice)
203
- # If node.value is not our arrayName, just return node
204
- if not (isinstance(node.value, ast.Name) and node.value.id == self.arrayName):
205
- return node
206
-
207
- # Handle scalar array access
208
- if isinstance(node.slice, ast.Attribute):
209
- memberName = self.extract_member_name(node.slice)
210
- if memberName:
211
- self.substitutions[memberName] = ('scalar', node)
212
- return ast.Name(id=memberName, ctx=ast.Load())
213
-
214
- # Handle array slice access
215
- if isinstance(node.slice, ast.Tuple) and node.slice.elts:
216
- firstElement = node.slice.elts[0]
217
- memberName = self.extract_member_name(firstElement)
218
- sliceRemainder = [self.visit(elem) for elem in node.slice.elts[1:]]
219
- if memberName:
220
- self.substitutions[memberName] = ('array', node)
221
- if len(sliceRemainder) == 0:
222
- return ast.Name(id=memberName, ctx=ast.Load())
223
- 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())
224
-
225
- # If single-element tuple, unwrap
226
- if isinstance(node.slice, ast.Tuple) and len(node.slice.elts) == 1:
227
- node.slice = node.slice.elts[0]
228
-
229
- return node
230
-
231
- def visit_FunctionDef(self, node: ast.FunctionDef) -> ast.FunctionDef:
232
- node = cast(ast.FunctionDef, self.generic_visit(node))
233
-
234
- initializations = []
235
- for name, (kind, original_node) in self.substitutions.items():
236
- if kind == 'scalar':
237
- initializations.append(ast.Assign(targets=[ast.Name(id=name, ctx=ast.Store())], value=original_node))
238
- else: # array
239
- initializations.append(
240
- ast.Assign(
241
- targets=[ast.Name(id=name, ctx=ast.Store())],
242
- value=ast.Subscript(value=ast.Name(id=self.arrayName, ctx=ast.Load()),
243
- slice=ast.Attribute(value=ast.Attribute(
244
- value=ast.Name(id=self.enumIndexClass.__name__, ctx=ast.Load()),
245
- attr=name, ctx=ast.Load()), attr='value', ctx=ast.Load()), ctx=ast.Load())))
246
-
247
- node.body = initializations + node.body
248
- return node
249
-
250
- def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
251
- # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
252
- for decoratorItem in astCallable.decorator_list.copy():
253
- import warnings
254
- astCallable.decorator_list.remove(decoratorItem)
255
- warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
256
- return astCallable
257
- def isThisNodeNumbaJitCall(node: ast.AST) -> bool:
258
- return (isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == Z0Z_getDecoratorCallable())
259
- def isThisNodeJitCall(node: ast.AST) -> bool:
260
- return (isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == Z0Z_getDecoratorCallable())
261
- def isThisNodeNumbaJitDecorator(node: ast.AST) -> bool:
262
- return isThisNodeNumbaJitCall(node) or isThisNodeJitCall(node)
263
- def Z0Z_generalizeThis(FunctionDefTarget: ast.FunctionDef, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, ParametersNumba | None]:
264
- def recycleParametersNumba(decorator: ast.Call) -> Dict[str, Any]:
265
- parametersNumbaExtracted: Dict[str, Any] = {}
266
- for keywordItem in decorator.keywords:
267
- if isinstance(keywordItem.value, ast.Constant) and keywordItem.arg is not None:
268
- parametersNumbaExtracted[keywordItem.arg] = keywordItem.value.value
269
- return parametersNumbaExtracted
270
-
271
- for decorator in FunctionDefTarget.decorator_list.copy():
272
- if isThisNodeNumbaJitDecorator(decorator):
273
- decorator = cast(ast.Call, decorator)
274
- if parametersNumba is None:
275
- parametersNumbaSherpa = recycleParametersNumba(decorator)
276
- if (HunterIsSureThereAreBetterWaysToDoThis := True):
277
- if parametersNumbaSherpa:
278
- parametersNumba = cast(ParametersNumba, parametersNumbaSherpa)
279
- FunctionDefTarget.decorator_list.remove(decorator)
280
-
281
- return FunctionDefTarget, parametersNumba
282
-
283
- def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
284
- datatypeModuleDecorator = Z0Z_getDatatypeModuleScalar()
285
- def make_arg4parameter(signatureElement: ast.arg):
286
- if isinstance(signatureElement.annotation, ast.Subscript) and isinstance(signatureElement.annotation.slice, ast.Tuple):
287
- annotationShape = signatureElement.annotation.slice.elts[0]
288
- if isinstance(annotationShape, ast.Subscript) and isinstance(annotationShape.slice, ast.Tuple):
289
- shapeAsListSlices: Sequence[ast.expr] = [ast.Slice() for axis in range(len(annotationShape.slice.elts))]
290
- shapeAsListSlices[-1] = ast.Slice(step=ast.Constant(value=1))
291
- shapeAST = ast.Tuple(elts=list(shapeAsListSlices), ctx=ast.Load())
292
- else:
293
- shapeAST = ast.Slice(step=ast.Constant(value=1))
294
-
295
- annotationDtype = signatureElement.annotation.slice.elts[1]
296
- if (isinstance(annotationDtype, ast.Subscript) and isinstance(annotationDtype.slice, ast.Attribute)):
297
- datatypeAST = annotationDtype.slice.attr
298
- else:
299
- datatypeAST = None
300
-
301
- ndarrayName = signatureElement.arg
302
- Z0Z_hacky_dtype = hackSSOTdatatype(ndarrayName)
303
- datatype_attr = datatypeAST or Z0Z_hacky_dtype
304
- allImports.addImportFromStr(datatypeModuleDecorator, datatype_attr)
305
- datatypeNumba = ast.Name(id=datatype_attr, ctx=ast.Load())
306
-
307
- return ast.Subscript(value=datatypeNumba, slice=shapeAST, ctx=ast.Load())
308
-
309
- list_argsDecorator: Sequence[ast.expr] = []
310
-
311
- list_arg4signature_or_function: Sequence[ast.expr] = []
312
- for parameter in FunctionDefTarget.args.args:
313
- signatureElement = make_arg4parameter(parameter)
314
- if signatureElement:
315
- list_arg4signature_or_function.append(signatureElement)
316
-
317
- if FunctionDefTarget.returns and isinstance(FunctionDefTarget.returns, ast.Name):
318
- theReturn: ast.Name = FunctionDefTarget.returns
319
- list_argsDecorator = [cast(ast.expr, ast.Call(func=ast.Name(id=theReturn.id, ctx=ast.Load())
320
- , args=list_arg4signature_or_function if list_arg4signature_or_function else [] , keywords=[] ) )]
321
- elif list_arg4signature_or_function:
322
- list_argsDecorator = [cast(ast.expr, ast.Tuple(elts=list_arg4signature_or_function, ctx=ast.Load()))]
323
-
324
- FunctionDefTarget, parametersNumba = Z0Z_generalizeThis(FunctionDefTarget, parametersNumba)
325
- FunctionDefTarget = Z0Z_UnhandledDecorators(FunctionDefTarget)
326
- if parametersNumba is None:
327
- parametersNumba = parametersNumbaDEFAULT
328
- listDecoratorKeywords = [ast.keyword(arg=parameterName, value=ast.Constant(value=parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
329
-
330
- decoratorModule = Z0Z_getDatatypeModuleScalar()
331
- decoratorCallable = Z0Z_getDecoratorCallable()
332
- allImports.addImportFromStr(decoratorModule, decoratorCallable)
333
- astDecorator = ast.Call(
334
- func=ast.Name(id=decoratorCallable, ctx=ast.Load())
335
- , args=list_argsDecorator if list_argsDecorator else []
336
- , keywords=listDecoratorKeywords)
337
-
338
- FunctionDefTarget.decorator_list = [astDecorator]
339
- return FunctionDefTarget, allImports
340
-
341
- def makeDecoratorJobNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
342
- decoratorCallable = Z0Z_getDecoratorCallable()
343
- def convertToPlainJit(node: ast.Call) -> ast.Call:
344
- node.func = ast.Name(id=decoratorCallable, ctx=ast.Load())
345
- return node
346
-
347
- FunctionDefTarget, parametersNumba = Z0Z_generalizeThis(FunctionDefTarget, parametersNumba)
348
-
349
- FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
350
- if isThisNodeNumbaJitCall(FunctionDefTarget.decorator_list[0]):
351
- FunctionDefTarget.decorator_list[0] = convertToPlainJit(cast(ast.Call, FunctionDefTarget.decorator_list[0]))
352
-
353
- return FunctionDefTarget, allImports
354
-
355
- def inlineOneCallable(pythonSource: str, callableTarget: str, parametersNumba: Optional[ParametersNumba]=None, unpackArrays: Optional[bool]=False) -> str:
356
- astModule: ast.Module = ast.parse(pythonSource, type_comments=True)
357
- allImports = UniversalImportTracker()
358
-
359
- for statement in astModule.body:
360
- if isinstance(statement, (ast.Import, ast.ImportFrom)):
361
- allImports.addAst(statement)
362
-
363
- dictionaryFunctionDef = {statement.name: statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
364
- callableInlinerWorkhorse = RecursiveInliner(dictionaryFunctionDef)
365
- FunctionDefTarget = callableInlinerWorkhorse.inlineFunctionBody(callableTarget)
366
-
367
- if not FunctionDefTarget:
368
- raise FREAKOUT
369
-
370
- ast.fix_missing_locations(FunctionDefTarget)
371
-
372
- FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
373
-
374
- if unpackArrays:
375
- for tupleUnpack in [(indexMy, 'my'), (indexTrack, 'track')]:
376
- unpacker = UnpackArrays(*tupleUnpack)
377
- FunctionDefTarget = cast(ast.FunctionDef, unpacker.visit(FunctionDefTarget))
378
- ast.fix_missing_locations(FunctionDefTarget)
379
-
380
- moduleAST = ast.Module(body=cast(List[ast.stmt], allImports.makeListAst() + [FunctionDefTarget]), type_ignores=[])
381
- ast.fix_missing_locations(moduleAST)
382
- moduleSource = ast.unparse(moduleAST)
383
- return moduleSource
384
-
385
- def makeDispatcherNumba(pythonSource: str, callableTarget: str, listStuffYouOughtaKnow: List[youOughtaKnow]) -> str:
386
- astSource = ast.parse(pythonSource)
387
- allImports = UniversalImportTracker()
388
-
389
- for statement in astSource.body:
390
- if isinstance(statement, (ast.Import, ast.ImportFrom)):
391
- allImports.addAst(statement)
392
-
393
- for stuff in listStuffYouOughtaKnow:
394
- statement = stuff.astForCompetentProgrammers
395
- if isinstance(statement, (ast.Import, ast.ImportFrom)):
396
- allImports.addAst(statement)
397
-
398
- FunctionDefTarget = next((node for node in astSource.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
399
-
400
- if not FunctionDefTarget:
401
- raise ValueError(f"Could not find function {callableTarget} in source code")
402
-
403
- FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumbaFailEarly)
404
-
405
- astModule = ast.Module(body=cast(List[ast.stmt], allImports.makeListAst() + [FunctionDefTarget]), type_ignores=[])
406
-
407
- ast.fix_missing_locations(astModule)
408
- return ast.unparse(astModule)
409
-
410
- def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: Optional[str]=None) -> str:
411
- """Converts a NumPy array into a compressed string representation using run-length encoding (RLE).
412
-
413
- This function takes a NumPy array and converts it into an optimized string representation by:
414
- 1. Compressing consecutive sequences of numbers into range objects
415
- 2. Minimizing repeated zeros using array multiplication syntax
416
- 3. Converting the result into a valid Python array initialization statement
417
-
418
- Parameters:
419
- arrayTarget (numpy.ndarray): The input NumPy array to be converted
420
- identifierName (str): The variable name to use in the output string
421
-
422
- Returns:
423
- str: A string containing Python code that recreates the input array in compressed form.
424
- Format: "{identifierName} = numpy.array({compressed_data}, dtype=numpy.{dtype})"
425
- """
426
-
427
- def compressRangesNDArrayNoFlatten(arraySlice):
428
- if isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim > 1:
429
- return [compressRangesNDArrayNoFlatten(arraySlice[index]) for index in range(arraySlice.shape[0])]
430
- elif isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim == 1:
431
- listWithRanges = []
432
- for group in more_itertools.consecutive_groups(arraySlice.tolist()):
433
- ImaSerious = list(group)
434
- if len(ImaSerious) <= 4:
435
- listWithRanges += ImaSerious
436
- else:
437
- ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
438
- listWithRanges += ImaRange
439
- return listWithRanges
440
- return arraySlice
441
-
442
- arrayAsNestedLists = compressRangesNDArrayNoFlatten(arrayTarget)
443
-
444
- stringMinimized = python_minifier.minify(str(arrayAsNestedLists))
445
- commaZeroMaximum = arrayTarget.shape[-1] - 1
446
- stringMinimized = stringMinimized.replace('[0' + ',0'*commaZeroMaximum + ']', '[0]*'+str(commaZeroMaximum+1))
447
- for countZeros in range(commaZeroMaximum, 2, -1):
448
- stringMinimized = stringMinimized.replace(',0'*countZeros + ']', ']+[0]*'+str(countZeros))
449
-
450
- stringMinimized = stringMinimized.replace('range', '*range')
451
-
452
- if identifierName:
453
- return f"{identifierName} = array({stringMinimized}, dtype={arrayTarget.dtype})"
454
- return stringMinimized
455
-
456
- def moveArrayTo_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
457
- arrayType = type(argData)
458
- moduleConstructor = arrayType.__module__
459
- constructorName = arrayType.__name__
460
- # NOTE hack
461
- constructorName = constructorName.replace('ndarray', 'array')
462
- argData_dtype: numpy.dtype = argData.dtype
463
- argData_dtypeName = argData.dtype.name
464
-
465
- allImports.addImportFromStr(moduleConstructor, constructorName)
466
- allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
467
-
468
- onlyDataRLE = makeStrRLEcompacted(argData)
469
- astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
470
- dataAst = astStatement.value
471
-
472
- arrayCall = ast.Call(
473
- func=ast.Name(id=constructorName, ctx=ast.Load())
474
- , args=[dataAst]
475
- , keywords=[ast.keyword(arg='dtype' , value=ast.Name(id=argData_dtypeName , ctx=ast.Load()) ) ] )
476
-
477
- assignment = ast.Assign( targets=[ast.Name(id=astArg.arg, ctx=ast.Store())], value=arrayCall )
478
- FunctionDefTarget.body.insert(0, assignment)
479
- FunctionDefTarget.args.args.remove(astArg)
480
-
481
- return FunctionDefTarget, allImports
482
-
483
- def evaluateArrayIn_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
484
- arrayType = type(argData)
485
- moduleConstructor = arrayType.__module__
486
- constructorName = arrayType.__name__
487
- # NOTE hack
488
- constructorName = constructorName.replace('ndarray', 'array')
489
- allImports.addImportFromStr(moduleConstructor, constructorName)
490
-
491
- for stmt in FunctionDefTarget.body.copy():
492
- if isinstance(stmt, ast.Assign):
493
- if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
494
- astAssignee: ast.Name = stmt.targets[0]
495
- argData_dtypeName = hackSSOTdatatype(astAssignee.id)
496
- allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
497
- astSubscript: ast.Subscript = stmt.value
498
- if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
499
- indexAs_astAttribute: ast.Attribute = astSubscript.slice
500
- indexAsStr = ast.unparse(indexAs_astAttribute)
501
- argDataSlice = argData[eval(indexAsStr)]
502
-
503
- onlyDataRLE = makeStrRLEcompacted(argDataSlice)
504
- astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
505
- dataAst = astStatement.value
506
-
507
- arrayCall = ast.Call(
508
- func=ast.Name(id=constructorName, ctx=ast.Load()) , args=[dataAst]
509
- , keywords=[ast.keyword(arg='dtype', value=ast.Name(id=argData_dtypeName, ctx=ast.Load()) ) ] )
510
-
511
- assignment = ast.Assign( targets=[astAssignee], value=arrayCall )
512
- FunctionDefTarget.body.insert(0, assignment)
513
- FunctionDefTarget.body.remove(stmt)
514
-
515
- FunctionDefTarget.args.args.remove(astArg)
516
- return FunctionDefTarget, allImports
517
-
518
- def evaluate_argIn_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg, argData: numpy.ndarray, Z0Z_listChaff: List[str], allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
519
- moduleConstructor = Z0Z_getDatatypeModuleScalar()
520
- for stmt in FunctionDefTarget.body.copy():
521
- if isinstance(stmt, ast.Assign):
522
- if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
523
- astAssignee: ast.Name = stmt.targets[0]
524
- argData_dtypeName = hackSSOTdatatype(astAssignee.id)
525
- allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
526
- astSubscript: ast.Subscript = stmt.value
527
- if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == astArg.arg and isinstance(astSubscript.slice, ast.Attribute):
528
- indexAs_astAttribute: ast.Attribute = astSubscript.slice
529
- indexAsStr = ast.unparse(indexAs_astAttribute)
530
- argDataSlice: int = argData[eval(indexAsStr)].item()
531
- astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[ast.Constant(value=argDataSlice)], keywords=[])
532
- assignment = ast.Assign(targets=[astAssignee], value=astCall)
533
- if astAssignee.id not in Z0Z_listChaff:
534
- FunctionDefTarget.body.insert(0, assignment)
535
- FunctionDefTarget.body.remove(stmt)
536
- FunctionDefTarget.args.args.remove(astArg)
537
- return FunctionDefTarget, allImports
538
-
539
- def evaluateAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
540
- moduleConstructor = Z0Z_getDatatypeModuleScalar()
541
- for stmt in FunctionDefTarget.body.copy():
542
- if isinstance(stmt, ast.AnnAssign):
543
- if isinstance(stmt.target, ast.Name) and isinstance(stmt.value, ast.Constant):
544
- astAssignee: ast.Name = stmt.target
545
- argData_dtypeName = hackSSOTdatatype(astAssignee.id)
546
- allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
547
- astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[stmt.value], keywords=[])
548
- assignment = ast.Assign(targets=[astAssignee], value=astCall)
549
- FunctionDefTarget.body.insert(0, assignment)
550
- FunctionDefTarget.body.remove(stmt)
551
- return FunctionDefTarget, allImports
552
-
553
- def removeIdentifierFrom_body(FunctionDefTarget: ast.FunctionDef, astArg: ast.arg) -> ast.FunctionDef:
554
- for stmt in FunctionDefTarget.body.copy():
555
- if isinstance(stmt, ast.Assign):
556
- if isinstance(stmt.targets[0], ast.Subscript) and isinstance(stmt.targets[0].value, ast.Name):
557
- if stmt.targets[0].value.id == astArg.arg:
558
- FunctionDefTarget.body.remove(stmt)
559
- FunctionDefTarget.args.args.remove(astArg)
560
- return FunctionDefTarget
561
-
562
- def astObjectToAstConstant(FunctionDefTarget: ast.FunctionDef, object: str, value: int) -> ast.FunctionDef:
563
- """
564
- Replaces nodes in astFunction matching the AST of the string `object`
565
- with a constant node holding the provided value.
566
- """
567
- targetExpression = ast.parse(object, mode='eval').body
568
- targetDump = ast.dump(targetExpression, annotate_fields=False)
569
-
570
- def findNode(node: ast.AST) -> bool:
571
- return ast.dump(node, annotate_fields=False) == targetDump
572
-
573
- def replaceWithConstant(node: ast.AST) -> ast.AST:
574
- return ast.copy_location(ast.Constant(value=value), node)
575
-
576
- transformer = NodeReplacer(findNode, replaceWithConstant)
577
- newFunction = cast(ast.FunctionDef, transformer.visit(FunctionDefTarget))
578
- ast.fix_missing_locations(newFunction)
579
- return newFunction
580
-
581
- def astNameToAstConstant(FunctionDefTarget: ast.FunctionDef, name: str, value: int) -> ast.FunctionDef:
582
- def findName(node: ast.AST) -> bool:
583
- return isinstance(node, ast.Name) and node.id == name
584
-
585
- def replaceWithConstant(node: ast.AST) -> ast.AST:
586
- return ast.copy_location(ast.Constant(value=value), node)
587
-
588
- return cast(ast.FunctionDef, NodeReplacer(findName, replaceWithConstant).visit(FunctionDefTarget))
589
-
590
- def makeLauncherJobNumba(callableTarget: str, pathFilenameFoldsTotal: pathlib.Path) -> ast.Module:
591
- linesLaunch = f"""
3
+ from mapFolding.someAssemblyRequired.synthesizeNumbaGeneralized import *
4
+
5
+ def insertArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker, unrollSlices: Optional[int]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
6
+ arrayType = type(arrayTarget)
7
+ moduleConstructor = arrayType.__module__
8
+ constructorName = arrayType.__name__
9
+ # NOTE hack
10
+ constructorName = constructorName.replace('ndarray', 'array')
11
+ argData_dtype: numpy.dtype = arrayTarget.dtype
12
+ argData_dtypeName = arrayTarget.dtype.name
13
+
14
+ allImports.addImportFromStr(moduleConstructor, constructorName)
15
+ allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
16
+
17
+ def insertAssign(assignee: str, arraySlice: numpy.ndarray) -> None:
18
+ nonlocal FunctionDefTarget
19
+ onlyDataRLE = makeStrRLEcompacted(arraySlice) #NOTE
20
+ astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
21
+ dataAst = astStatement.value
22
+
23
+ arrayCall = Then.make_astCall(name=constructorName, args=[dataAst], dictionaryKeywords={'dtype': ast.Name(id=argData_dtypeName, ctx=ast.Load())})
24
+
25
+ assignment = ast.Assign(targets=[ast.Name(id=assignee, ctx=ast.Store())], value=arrayCall)#NOTE
26
+ FunctionDefTarget.body.insert(0, assignment)
27
+
28
+ if not unrollSlices:
29
+ insertAssign(identifier, arrayTarget)
30
+ else:
31
+ for index, arraySlice in enumerate(arrayTarget):
32
+ insertAssign(f"{identifier}_{index}", arraySlice)
33
+
34
+ return FunctionDefTarget, allImports
35
+
36
+ def findAndReplaceArrayIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
37
+ arrayType = type(arrayTarget)
38
+ moduleConstructor = arrayType.__module__
39
+ constructorName = arrayType.__name__
40
+ # NOTE hack
41
+ constructorName = constructorName.replace('ndarray', 'array')
42
+ allImports.addImportFromStr(moduleConstructor, constructorName)
43
+
44
+ for stmt in FunctionDefTarget.body.copy():
45
+ if isinstance(stmt, ast.Assign):
46
+ if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
47
+ astAssignee: ast.Name = stmt.targets[0]
48
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
49
+ allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
50
+ astSubscript: ast.Subscript = stmt.value
51
+ if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == identifier and isinstance(astSubscript.slice, ast.Attribute):
52
+ indexAs_astAttribute: ast.Attribute = astSubscript.slice
53
+ indexAsStr = ast.unparse(indexAs_astAttribute)
54
+ argDataSlice = arrayTarget[eval(indexAsStr)]
55
+
56
+ onlyDataRLE = makeStrRLEcompacted(argDataSlice)
57
+ astStatement = cast(ast.Expr, ast.parse(onlyDataRLE).body[0])
58
+ dataAst = astStatement.value
59
+
60
+ arrayCall = Then.make_astCall(name=constructorName, args=[dataAst], dictionaryKeywords={'dtype': ast.Name(id=argData_dtypeName, ctx=ast.Load())})
61
+
62
+ assignment = ast.Assign( targets=[astAssignee], value=arrayCall )
63
+ FunctionDefTarget.body.insert(0, assignment)
64
+ FunctionDefTarget.body.remove(stmt)
65
+ return FunctionDefTarget, allImports
66
+
67
+ def findAndReplaceArraySubscriptIn_body(FunctionDefTarget: ast.FunctionDef, identifier: str, arrayTarget: numpy.ndarray, Z0Z_listChaff: List[str], allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
68
+ moduleConstructor = Z0Z_getDatatypeModuleScalar()
69
+ for stmt in FunctionDefTarget.body.copy():
70
+ if isinstance(stmt, ast.Assign):
71
+ if isinstance(stmt.targets[0], ast.Name) and isinstance(stmt.value, ast.Subscript):
72
+ astAssignee: ast.Name = stmt.targets[0]
73
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
74
+ allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
75
+ astSubscript: ast.Subscript = stmt.value
76
+ if isinstance(astSubscript.value, ast.Name) and astSubscript.value.id == identifier and isinstance(astSubscript.slice, ast.Attribute):
77
+ indexAs_astAttribute: ast.Attribute = astSubscript.slice
78
+ indexAsStr = ast.unparse(indexAs_astAttribute)
79
+ argDataSlice: int = arrayTarget[eval(indexAsStr)].item()
80
+ astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[ast.Constant(value=argDataSlice)], keywords=[])
81
+ assignment = ast.Assign(targets=[astAssignee], value=astCall)
82
+ if astAssignee.id not in Z0Z_listChaff:
83
+ FunctionDefTarget.body.insert(0, assignment)
84
+ FunctionDefTarget.body.remove(stmt)
85
+ return FunctionDefTarget, allImports
86
+
87
+ def removeAssignTargetFrom_body(FunctionDefTarget: ast.FunctionDef, identifier: str) -> ast.FunctionDef:
88
+ # Remove assignment nodes where the target is either a Subscript referencing `identifier` or satisfies ifThis.nameIs(identifier).
89
+ def predicate(astNode: ast.AST) -> bool:
90
+ if not isinstance(astNode, ast.Assign) or not astNode.targets:
91
+ return False
92
+ targetNode = astNode.targets[0]
93
+ return (isinstance(targetNode, ast.Subscript) and isinstance(targetNode.value, ast.Name) and targetNode.value.id == identifier) or ifThis.nameIs(identifier)(targetNode)
94
+ def replacementBuilder(astNode: ast.AST) -> Optional[ast.stmt]:
95
+ # Returning None removes the node.
96
+ return None
97
+ FunctionDefSherpa = NodeReplacer(predicate, replacementBuilder).visit(FunctionDefTarget)
98
+ if not FunctionDefSherpa:
99
+ raise FREAKOUT("Dude, where's my function?")
100
+ else:
101
+ FunctionDefTarget = cast(ast.FunctionDef, FunctionDefSherpa)
102
+ ast.fix_missing_locations(FunctionDefTarget)
103
+ return FunctionDefTarget
104
+
105
+ def findAndReplaceAnnAssignIn_body(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
106
+ moduleConstructor = Z0Z_getDatatypeModuleScalar()
107
+ for stmt in FunctionDefTarget.body.copy():
108
+ if isinstance(stmt, ast.AnnAssign):
109
+ if isinstance(stmt.target, ast.Name) and isinstance(stmt.value, ast.Constant):
110
+ astAssignee: ast.Name = stmt.target
111
+ argData_dtypeName = hackSSOTdatatype(astAssignee.id)
112
+ allImports.addImportFromStr(moduleConstructor, argData_dtypeName)
113
+ astCall = ast.Call(func=ast.Name(id=argData_dtypeName, ctx=ast.Load()) , args=[stmt.value], keywords=[])
114
+ assignment = ast.Assign(targets=[astAssignee], value=astCall)
115
+ FunctionDefTarget.body.insert(0, assignment)
116
+ FunctionDefTarget.body.remove(stmt)
117
+ return FunctionDefTarget, allImports
118
+
119
+ def findThingyReplaceWithConstantIn_body(FunctionDefTarget: ast.FunctionDef, object: str, value: int) -> ast.FunctionDef:
120
+ """
121
+ Replaces nodes in astFunction matching the AST of the string `object`
122
+ with a constant node holding the provided value.
123
+ """
124
+ targetExpression = ast.parse(object, mode='eval').body
125
+ targetDump = ast.dump(targetExpression, annotate_fields=False)
126
+
127
+ def findNode(node: ast.AST) -> bool:
128
+ return ast.dump(node, annotate_fields=False) == targetDump
129
+
130
+ def replaceWithConstant(node: ast.AST) -> ast.AST:
131
+ return ast.copy_location(ast.Constant(value=value), node)
132
+
133
+ transformer = NodeReplacer(findNode, replaceWithConstant)
134
+ newFunction = cast(ast.FunctionDef, transformer.visit(FunctionDefTarget))
135
+ ast.fix_missing_locations(newFunction)
136
+ return newFunction
137
+
138
+ def findAstNameReplaceWithConstantIn_body(FunctionDefTarget: ast.FunctionDef, name: str, value: int) -> ast.FunctionDef:
139
+ def findName(node: ast.AST) -> bool:
140
+ return isinstance(node, ast.Name) and node.id == name
141
+
142
+ def replaceWithConstant(node: ast.AST) -> ast.AST:
143
+ return ast.copy_location(ast.Constant(value=value), node)
144
+
145
+ return cast(ast.FunctionDef, NodeReplacer(findName, replaceWithConstant).visit(FunctionDefTarget))
146
+
147
+ def insertReturnStatementIn_body(FunctionDefTarget: ast.FunctionDef, arrayTarget: numpy.ndarray, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
148
+ """Add multiplication and return statement to function, properly constructing AST nodes."""
149
+ # Create AST for multiplication operation
150
+ multiplicand = Z0Z_identifierCountFolds
151
+ datatype = hackSSOTdatatype(multiplicand)
152
+ multiplyOperation = ast.BinOp(
153
+ left=ast.Name(id=multiplicand, ctx=ast.Load()),
154
+ op=ast.Mult(), right=ast.Constant(value=int(arrayTarget[-1])))
155
+
156
+ returnStatement = ast.Return(value=multiplyOperation)
157
+
158
+ datatypeModuleScalar = Z0Z_getDatatypeModuleScalar()
159
+ allImports.addImportFromStr(datatypeModuleScalar, datatype)
160
+
161
+ FunctionDefTarget.body.append(returnStatement)
162
+
163
+ return FunctionDefTarget, allImports
164
+
165
+ def findAndReplaceWhileLoopIn_body(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int) -> Any:
166
+ """
167
+ Unroll all nested while loops matching the condition that their test uses `iteratorName`.
168
+ """
169
+ # Helper transformer to replace iterator occurrences with a constant.
170
+ class ReplaceIterator(ast.NodeTransformer):
171
+ def __init__(self, iteratorName: str, constantValue: int) -> None:
172
+ super().__init__()
173
+ self.iteratorName = iteratorName
174
+ self.constantValue = constantValue
175
+
176
+ def visit_Name(self, node: ast.Name) -> ast.AST:
177
+ if node.id == self.iteratorName:
178
+ return ast.copy_location(ast.Constant(value=self.constantValue), node)
179
+ return self.generic_visit(node)
180
+
181
+ # NodeTransformer that finds while loops (even if deeply nested) and unrolls them.
182
+ class WhileLoopUnroller(ast.NodeTransformer):
183
+ def __init__(self, iteratorName: str, iterationsTotal: int) -> None:
184
+ super().__init__()
185
+ self.iteratorName = iteratorName
186
+ self.iterationsTotal = iterationsTotal
187
+
188
+ def visit_While(self, node: ast.While) -> List[ast.stmt]:
189
+ # Check if the while loop's test uses the iterator.
190
+ if isinstance(node.test, ast.Compare) and ifThis.nameIs(self.iteratorName)(node.test.left):
191
+ # Recurse the while loop body and remove AugAssign that increments the iterator.
192
+ cleanBodyStatements: List[ast.stmt] = []
193
+ for loopStatement in node.body:
194
+ # Recursively visit nested statements.
195
+ visitedStatement = self.visit(loopStatement)
196
+ # Remove direct AugAssign: iterator += 1.
197
+ if (isinstance(loopStatement, ast.AugAssign) and
198
+ isinstance(loopStatement.target, ast.Name) and
199
+ loopStatement.target.id == self.iteratorName and
200
+ isinstance(loopStatement.op, ast.Add) and
201
+ isinstance(loopStatement.value, ast.Constant) and
202
+ loopStatement.value.value == 1):
203
+ continue
204
+ cleanBodyStatements.append(visitedStatement)
205
+
206
+ newStatements: List[ast.stmt] = []
207
+ # Unroll using the filtered body.
208
+ for iterationIndex in range(self.iterationsTotal):
209
+ for loopStatement in cleanBodyStatements:
210
+ copiedStatement = copy.deepcopy(loopStatement)
211
+ replacer = ReplaceIterator(self.iteratorName, iterationIndex)
212
+ newStatement = replacer.visit(copiedStatement)
213
+ ast.fix_missing_locations(newStatement)
214
+ newStatements.append(newStatement)
215
+ # Optionally, process the orelse block.
216
+ if node.orelse:
217
+ for elseStmt in node.orelse:
218
+ visitedElse = self.visit(elseStmt)
219
+ if isinstance(visitedElse, list):
220
+ newStatements.extend(visitedElse)
221
+ else:
222
+ newStatements.append(visitedElse)
223
+ return newStatements
224
+ return [cast(ast.stmt, self.generic_visit(node))]
225
+
226
+ newFunctionDef = WhileLoopUnroller(iteratorName, iterationsTotal).visit(FunctionDefTarget)
227
+ ast.fix_missing_locations(newFunctionDef)
228
+ return newFunctionDef
229
+
230
+ def makeLauncherBasicJobNumba(callableTarget: str, pathFilenameFoldsTotal: pathlib.Path) -> ast.Module:
231
+ linesLaunch = f"""
592
232
  if __name__ == '__main__':
593
- import time
594
- timeStart = time.perf_counter()
595
- foldsTotal = {callableTarget}()
596
- print(foldsTotal, time.perf_counter() - timeStart)
597
- writeStream = open('{pathFilenameFoldsTotal.as_posix()}', 'w')
598
- writeStream.write(str(foldsTotal))
599
- writeStream.close()
233
+ import time
234
+ timeStart = time.perf_counter()
235
+ foldsTotal = {callableTarget}()
236
+ print(foldsTotal, time.perf_counter() - timeStart)
237
+ writeStream = open('{pathFilenameFoldsTotal.as_posix()}', 'w')
238
+ writeStream.write(str(foldsTotal))
239
+ writeStream.close()
600
240
  """
601
- return ast.parse(linesLaunch)
602
-
603
- def addReturnJobNumba(FunctionDefTarget: ast.FunctionDef, stateJob: computationState, allImports: UniversalImportTracker) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
604
- """Add multiplication and return statement to function, properly constructing AST nodes."""
605
- # Create AST for multiplication operation
606
- multiplicand = Z0Z_identifierCountFolds
607
- datatype = hackSSOTdatatype(multiplicand)
608
- multiplyOperation = ast.BinOp(
609
- left=ast.Name(id=multiplicand, ctx=ast.Load()),
610
- op=ast.Mult(), right=ast.Constant(value=int(stateJob['foldGroups'][-1])))
611
-
612
- returnStatement = ast.Return(value=multiplyOperation)
613
-
614
- datatypeModuleScalar = Z0Z_getDatatypeModuleScalar()
615
- allImports.addImportFromStr(datatypeModuleScalar, datatype)
616
- FunctionDefTarget.returns = ast.Name(id=datatype, ctx=ast.Load())
617
-
618
- FunctionDefTarget.body.append(returnStatement)
619
-
620
- return FunctionDefTarget, allImports
621
-
622
- def unrollWhileLoop(FunctionDefTarget: ast.FunctionDef, iteratorName: str, iterationsTotal: int, connectionGraph: numpy.ndarray[Tuple[int, int, int], numpy.dtype[numpy.integer[Any]]]) -> ast.FunctionDef:
623
- """
624
- Unroll the countGaps loop: in theDao, it is a while loop, of course.
625
- However, it could be written as `for indexDimension in range(dimensionsTotal):`.
626
- It is useful to note that it could also be written as `for indexDimension in range(connectionGraph.shape[0]):`.
627
- We will unroll the loop into a series of stateJob['my'][indexMy.dimensionsTotal]-many code blocks that are similar but not identical.
628
- In each code block, we know the value of the identifier `indexDimension`, so we replace the identifier with its value.
629
- Furthermore, we will split connectionGraph into arrays along the first axis.
630
- `connectionGraph[indexDimension, leaf1ndex, leafBelow[leafConnectee]]`
631
- `connectionGraph0[leaf1ndex, leafBelow[leafConnectee]]`
632
- `connectionGraph1[leaf1ndex, leafBelow[leafConnectee]]`
633
- `connectionGraphN[leaf1ndex, leafBelow[leafConnectee]]`
634
-
635
- After unrolling, we can remove three `indexDimension` statements: 1) the first initialization, which is really a memory allocation, 2) the loop initialization, and 3) the loop increment.
636
- """
637
- return FunctionDefTarget
241
+ return ast.parse(linesLaunch)
242
+
243
+ def makeAstModuleForOneCallable(pythonSource: str, callableTarget: str, parametersNumba: Optional[ParametersNumba]=None, inlineCallables: Optional[bool]=False , unpackArrays: Optional[bool]=False , allImports: Optional[UniversalImportTracker]=None ) -> str:
244
+ astModule: ast.Module = ast.parse(pythonSource, type_comments=True)
245
+
246
+ if allImports is None:
247
+ allImports = UniversalImportTracker()
248
+ for statement in astModule.body:
249
+ if isinstance(statement, (ast.Import, ast.ImportFrom)):
250
+ allImports.addAst(statement)
251
+
252
+ if inlineCallables:
253
+ dictionaryFunctionDef = {statement.name: statement for statement in astModule.body if isinstance(statement, ast.FunctionDef)}
254
+ callableInlinerWorkhorse = RecursiveInliner(dictionaryFunctionDef)
255
+ FunctionDefTarget = callableInlinerWorkhorse.inlineFunctionBody(callableTarget)
256
+ else:
257
+ FunctionDefTarget = next((node for node in astModule.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
258
+ if not FunctionDefTarget:
259
+ raise ValueError(f"Could not find function {callableTarget} in source code")
260
+
261
+ ast.fix_missing_locations(FunctionDefTarget)
262
+
263
+ FunctionDefTarget, allImports = decorateCallableWithNumba(FunctionDefTarget, allImports, parametersNumba)
264
+
265
+ # NOTE vestigial hardcoding
266
+ if unpackArrays:
267
+ for tupleUnpack in [(indexMy, 'my'), (indexTrack, 'track')]:
268
+ unpacker = UnpackArrays(*tupleUnpack)
269
+ FunctionDefTarget = cast(ast.FunctionDef, unpacker.visit(FunctionDefTarget))
270
+ ast.fix_missing_locations(FunctionDefTarget)
271
+
272
+ astModule = ast.Module(body=cast(List[ast.stmt], allImports.makeListAst() + [FunctionDefTarget]), type_ignores=[])
273
+ ast.fix_missing_locations(astModule)
274
+ return ast.unparse(astModule)
275
+
276
+ def decorateCallableWithNumba(FunctionDefTarget: ast.FunctionDef, allImports: UniversalImportTracker, parametersNumba: Optional[ParametersNumba]=None) -> Tuple[ast.FunctionDef, UniversalImportTracker]:
277
+ def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
278
+ # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
279
+ for decoratorItem in astCallable.decorator_list.copy():
280
+ import warnings
281
+ astCallable.decorator_list.remove(decoratorItem)
282
+ warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
283
+ return astCallable
284
+
285
+ def make_arg4parameter(signatureElement: ast.arg) -> ast.Subscript | None:
286
+ if isinstance(signatureElement.annotation, ast.Subscript) and isinstance(signatureElement.annotation.slice, ast.Tuple):
287
+ annotationShape = signatureElement.annotation.slice.elts[0]
288
+ if isinstance(annotationShape, ast.Subscript) and isinstance(annotationShape.slice, ast.Tuple):
289
+ shapeAsListSlices = [ast.Slice() for axis in range(len(annotationShape.slice.elts))]
290
+ shapeAsListSlices[-1] = ast.Slice(step=ast.Constant(value=1))
291
+ shapeAST = ast.Tuple(elts=list(shapeAsListSlices), ctx=ast.Load())
292
+ else:
293
+ shapeAST = ast.Slice(step=ast.Constant(value=1))
294
+
295
+ annotationDtype = signatureElement.annotation.slice.elts[1]
296
+ if (isinstance(annotationDtype, ast.Subscript) and isinstance(annotationDtype.slice, ast.Attribute)):
297
+ datatypeAST = annotationDtype.slice.attr
298
+ else:
299
+ datatypeAST = None
300
+
301
+ ndarrayName = signatureElement.arg
302
+ Z0Z_hacky_dtype = hackSSOTdatatype(ndarrayName)
303
+ datatype_attr = datatypeAST or Z0Z_hacky_dtype
304
+ allImports.addImportFromStr(datatypeModuleDecorator, datatype_attr)
305
+ datatypeNumba = ast.Name(id=datatype_attr, ctx=ast.Load())
306
+
307
+ return ast.Subscript(value=datatypeNumba, slice=shapeAST, ctx=ast.Load())
308
+ return
309
+
310
+ datatypeModuleDecorator = Z0Z_getDatatypeModuleScalar()
311
+ list_argsDecorator: Sequence[ast.expr] = []
312
+
313
+ list_arg4signature_or_function: List[ast.expr] = []
314
+ for parameter in FunctionDefTarget.args.args:
315
+ signatureElement = make_arg4parameter(parameter)
316
+ if signatureElement:
317
+ list_arg4signature_or_function.append(signatureElement)
318
+
319
+ if FunctionDefTarget.returns and isinstance(FunctionDefTarget.returns, ast.Name):
320
+ theReturn: ast.Name = FunctionDefTarget.returns
321
+ list_argsDecorator = [cast(ast.expr, ast.Call(func=ast.Name(id=theReturn.id, ctx=ast.Load())
322
+ , args=list_arg4signature_or_function if list_arg4signature_or_function else [] , keywords=[] ) )]
323
+ elif list_arg4signature_or_function:
324
+ list_argsDecorator = [cast(ast.expr, ast.Tuple(elts=list_arg4signature_or_function, ctx=ast.Load()))]
325
+
326
+ for decorator in FunctionDefTarget.decorator_list.copy():
327
+ if thisIsAnyNumbaJitDecorator(decorator):
328
+ decorator = cast(ast.Call, decorator)
329
+ if parametersNumba is None:
330
+ parametersNumbaSherpa = Then.copy_astCallKeywords(decorator)
331
+ if (HunterIsSureThereAreBetterWaysToDoThis := True):
332
+ if parametersNumbaSherpa:
333
+ parametersNumba = cast(ParametersNumba, parametersNumbaSherpa)
334
+ FunctionDefTarget.decorator_list.remove(decorator)
335
+
336
+ FunctionDefTarget = Z0Z_UnhandledDecorators(FunctionDefTarget)
337
+ if parametersNumba is None:
338
+ parametersNumba = parametersNumbaDEFAULT
339
+ listDecoratorKeywords = [ast.keyword(arg=parameterName, value=ast.Constant(value=parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
340
+
341
+ decoratorModule = Z0Z_getDatatypeModuleScalar()
342
+ decoratorCallable = Z0Z_getDecoratorCallable()
343
+ allImports.addImportFromStr(decoratorModule, decoratorCallable)
344
+ astDecorator = Then.make_astCall(decoratorCallable, list_argsDecorator, listDecoratorKeywords, None)
345
+
346
+ FunctionDefTarget.decorator_list = [astDecorator]
347
+ return FunctionDefTarget, allImports