mapFolding 0.8.4__py3-none-any.whl → 0.8.6__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 (36) hide show
  1. mapFolding/__init__.py +10 -6
  2. mapFolding/basecamp.py +3 -3
  3. mapFolding/beDRY.py +241 -68
  4. mapFolding/oeis.py +41 -26
  5. mapFolding/reference/hunterNumba.py +1 -1
  6. mapFolding/someAssemblyRequired/__init__.py +16 -15
  7. mapFolding/someAssemblyRequired/_theTypes.py +31 -13
  8. mapFolding/someAssemblyRequired/_tool_Make.py +13 -5
  9. mapFolding/someAssemblyRequired/_tool_Then.py +12 -5
  10. mapFolding/someAssemblyRequired/_toolboxAntecedents.py +131 -99
  11. mapFolding/someAssemblyRequired/_toolboxContainers.py +92 -15
  12. mapFolding/someAssemblyRequired/_toolboxPython.py +17 -31
  13. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +2 -2
  14. mapFolding/someAssemblyRequired/newInliner.py +22 -0
  15. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +65 -116
  16. mapFolding/someAssemblyRequired/toolboxNumba.py +364 -0
  17. mapFolding/someAssemblyRequired/transformationTools.py +262 -41
  18. mapFolding/syntheticModules/numbaCount_doTheNeedful.py +0 -1
  19. mapFolding/theSSOT.py +30 -33
  20. mapFolding/{filesystem.py → toolboxFilesystem.py} +90 -25
  21. {mapfolding-0.8.4.dist-info → mapfolding-0.8.6.dist-info}/METADATA +3 -2
  22. mapfolding-0.8.6.dist-info/RECORD +47 -0
  23. tests/conftest.py +30 -31
  24. tests/test_computations.py +8 -7
  25. tests/test_filesystem.py +2 -2
  26. tests/test_other.py +2 -2
  27. tests/test_tasks.py +3 -3
  28. mapFolding/noHomeYet.py +0 -32
  29. mapFolding/someAssemblyRequired/ingredientsNumba.py +0 -199
  30. mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +0 -156
  31. mapFolding/someAssemblyRequired/transformDataStructures.py +0 -235
  32. mapfolding-0.8.4.dist-info/RECORD +0 -49
  33. {mapfolding-0.8.4.dist-info → mapfolding-0.8.6.dist-info}/WHEEL +0 -0
  34. {mapfolding-0.8.4.dist-info → mapfolding-0.8.6.dist-info}/entry_points.txt +0 -0
  35. {mapfolding-0.8.4.dist-info → mapfolding-0.8.6.dist-info}/licenses/LICENSE +0 -0
  36. {mapfolding-0.8.4.dist-info → mapfolding-0.8.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,364 @@
1
+ """
2
+ Numba-specific ingredients for optimized code generation.
3
+
4
+ This module provides specialized tools, constants, and types specifically designed
5
+ for transforming Python code into Numba-accelerated implementations. It implements:
6
+
7
+ 1. A range of Numba jit decorator configurations for different optimization scenarios
8
+ 2. Functions to identify and manipulate Numba decorators in abstract syntax trees
9
+ 3. Utilities for applying appropriate Numba typing to transformed code
10
+ 4. Parameter management for Numba compilation options
11
+
12
+ The configurations range from conservative options that prioritize compatibility and
13
+ error detection to aggressive optimizations that maximize performance at the cost of
14
+ flexibility. While this module specifically targets Numba, its design follows the pattern
15
+ of generic code transformation tools in the package, allowing similar approaches to be
16
+ applied to other acceleration technologies.
17
+
18
+ This module works in conjunction with transformation tools to convert the general-purpose
19
+ algorithm implementation into a highly-optimized Numba version.
20
+ """
21
+
22
+ from collections.abc import Callable, Sequence
23
+ from mapFolding.toolboxFilesystem import getPathFilenameFoldsTotal, getPathRootJobDEFAULT
24
+ from mapFolding.someAssemblyRequired import IngredientsModule, LedgerOfImports, Make, NodeChanger, NodeTourist, RecipeSynthesizeFlow, Then, ast_Identifier, be, ifThis, parsePathFilename2astModule, str_nameDOTname, IngredientsFunction, ShatteredDataclass
25
+ from mapFolding.someAssemblyRequired.transformationTools import Z0Z_inlineThisFunctionWithTheseValues, Z0Z_lameFindReplace, Z0Z_makeDictionaryReplacementStatements, astModuleToIngredientsFunction, shatter_dataclassesDOTdataclass, write_astModule
26
+ from mapFolding.theSSOT import ComputationState, DatatypeFoldsTotal as TheDatatypeFoldsTotal, DatatypeElephino as TheDatatypeElephino, DatatypeLeavesTotal as TheDatatypeLeavesTotal
27
+
28
+ from numba.core.compiler import CompilerBase as numbaCompilerBase
29
+ from pathlib import Path, PurePosixPath
30
+ from typing import Any, cast, Final, TYPE_CHECKING, TypeAlias
31
+ import ast
32
+ import dataclasses
33
+
34
+ try:
35
+ from typing import NotRequired
36
+ except Exception:
37
+ from typing_extensions import NotRequired
38
+
39
+ if TYPE_CHECKING:
40
+ from typing import TypedDict
41
+ else:
42
+ TypedDict = dict[str,Any]
43
+
44
+ theNumbaFlow: RecipeSynthesizeFlow = RecipeSynthesizeFlow()
45
+
46
+ class ParametersNumba(TypedDict):
47
+ _dbg_extend_lifetimes: NotRequired[bool]
48
+ _dbg_optnone: NotRequired[bool]
49
+ _nrt: NotRequired[bool]
50
+ boundscheck: NotRequired[bool]
51
+ cache: bool
52
+ debug: NotRequired[bool]
53
+ error_model: str
54
+ fastmath: bool
55
+ forceinline: bool
56
+ forceobj: NotRequired[bool]
57
+ inline: str
58
+ locals: NotRequired[dict[str, Any]]
59
+ looplift: bool
60
+ no_cfunc_wrapper: bool
61
+ no_cpython_wrapper: bool
62
+ no_rewrites: NotRequired[bool]
63
+ nogil: NotRequired[bool]
64
+ nopython: bool
65
+ parallel: bool
66
+ pipeline_class: NotRequired[type[numbaCompilerBase]]
67
+ signature_or_function: NotRequired[Any | Callable[..., Any] | str | tuple[Any, ...]]
68
+ target: NotRequired[str]
69
+
70
+ parametersNumbaFailEarly: Final[ParametersNumba] = { '_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, }
71
+ """For a production function: speed is irrelevant, error discovery is paramount, must be compatible with anything downstream."""
72
+ parametersNumbaDefault: Final[ParametersNumba] = { '_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, }
73
+ """Middle of the road: fast, lean, but will talk to non-jitted functions."""
74
+ parametersNumbaParallelDEFAULT: Final[ParametersNumba] = { **parametersNumbaDefault, '_nrt': True, 'parallel': True, }
75
+ """Middle of the road: fast, lean, but will talk to non-jitted functions."""
76
+ parametersNumbaSuperJit: Final[ParametersNumba] = { **parametersNumbaDefault, 'no_cfunc_wrapper': True, 'no_cpython_wrapper': True, }
77
+ """Speed, no helmet, no talking to non-jitted functions."""
78
+ parametersNumbaSuperJitParallel: Final[ParametersNumba] = { **parametersNumbaSuperJit, '_nrt': True, 'parallel': True, }
79
+ """Speed, no helmet, concurrency, no talking to non-jitted functions."""
80
+ parametersNumbaMinimum: Final[ParametersNumba] = { '_nrt': True, 'boundscheck': True, 'cache': True, 'error_model': 'numpy', 'fastmath': True, 'forceinline': False, 'inline': 'always', 'looplift': False, 'no_cfunc_wrapper': False, 'no_cpython_wrapper': False, 'nopython': False, 'forceobj': True, 'parallel': False, }
81
+
82
+ Z0Z_numbaDataTypeModule: str_nameDOTname = 'numba'
83
+ Z0Z_decoratorCallable: ast_Identifier = 'jit'
84
+
85
+ def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, parametersNumba: ParametersNumba | None = None) -> IngredientsFunction:
86
+ def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
87
+ # TODO: more explicit handling of decorators. I'm able to ignore this because I know `algorithmSource` doesn't have any decorators.
88
+ for decoratorItem in astCallable.decorator_list.copy():
89
+ import warnings
90
+ astCallable.decorator_list.remove(decoratorItem)
91
+ warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
92
+ return astCallable
93
+
94
+ def makeSpecialSignatureForNumba(signatureElement: ast.arg) -> ast.Subscript | ast.Name | None: # type: ignore
95
+ if isinstance(signatureElement.annotation, ast.Subscript) and isinstance(signatureElement.annotation.slice, ast.Tuple):
96
+ annotationShape: ast.expr = signatureElement.annotation.slice.elts[0]
97
+ if isinstance(annotationShape, ast.Subscript) and isinstance(annotationShape.slice, ast.Tuple):
98
+ shapeAsListSlices: list[ast.Slice] = [ast.Slice() for _axis in range(len(annotationShape.slice.elts))]
99
+ shapeAsListSlices[-1] = ast.Slice(step=ast.Constant(value=1))
100
+ shapeAST: ast.Slice | ast.Tuple = ast.Tuple(elts=list(shapeAsListSlices), ctx=ast.Load())
101
+ else:
102
+ shapeAST = ast.Slice(step=ast.Constant(value=1))
103
+
104
+ annotationDtype: ast.expr = signatureElement.annotation.slice.elts[1]
105
+ if (isinstance(annotationDtype, ast.Subscript) and isinstance(annotationDtype.slice, ast.Attribute)):
106
+ datatypeAST = annotationDtype.slice.attr
107
+ else:
108
+ datatypeAST = None
109
+
110
+ ndarrayName = signatureElement.arg
111
+ Z0Z_hacky_dtype: str = ndarrayName
112
+ datatype_attr = datatypeAST or Z0Z_hacky_dtype
113
+ ingredientsFunction.imports.addImportFrom_asStr(datatypeModuleDecorator, datatype_attr)
114
+ datatypeNumba = ast.Name(id=datatype_attr, ctx=ast.Load())
115
+
116
+ return ast.Subscript(value=datatypeNumba, slice=shapeAST, ctx=ast.Load())
117
+
118
+ elif isinstance(signatureElement.annotation, ast.Name):
119
+ return signatureElement.annotation
120
+ return None
121
+
122
+ datatypeModuleDecorator: str = Z0Z_numbaDataTypeModule
123
+ list_argsDecorator: Sequence[ast.expr] = []
124
+
125
+ list_arg4signature_or_function: list[ast.expr] = []
126
+ for parameter in ingredientsFunction.astFunctionDef.args.args:
127
+ # For now, let Numba infer them.
128
+ continue
129
+ # signatureElement: ast.Subscript | ast.Name | None = makeSpecialSignatureForNumba(parameter)
130
+ # if signatureElement:
131
+ # list_arg4signature_or_function.append(signatureElement)
132
+
133
+ if ingredientsFunction.astFunctionDef.returns and isinstance(ingredientsFunction.astFunctionDef.returns, ast.Name):
134
+ theReturn: ast.Name = ingredientsFunction.astFunctionDef.returns
135
+ list_argsDecorator = [cast(ast.expr, ast.Call(func=ast.Name(id=theReturn.id, ctx=ast.Load())
136
+ , args=list_arg4signature_or_function if list_arg4signature_or_function else [], keywords=[] ) )]
137
+ elif list_arg4signature_or_function:
138
+ list_argsDecorator = [cast(ast.expr, ast.Tuple(elts=list_arg4signature_or_function, ctx=ast.Load()))]
139
+
140
+ ingredientsFunction.astFunctionDef = Z0Z_UnhandledDecorators(ingredientsFunction.astFunctionDef)
141
+ if parametersNumba is None:
142
+ parametersNumba = parametersNumbaDefault
143
+ listDecoratorKeywords: list[ast.keyword] = [Make.keyword(parameterName, Make.Constant(parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
144
+
145
+ decoratorModule = Z0Z_numbaDataTypeModule
146
+ decoratorCallable = Z0Z_decoratorCallable
147
+ ingredientsFunction.imports.addImportFrom_asStr(decoratorModule, decoratorCallable)
148
+ # Leave this line in so that global edits will change it.
149
+ astDecorator: ast.Call = Make.Call(Make.Name(decoratorCallable), list_argsDecorator, listDecoratorKeywords)
150
+ astDecorator: ast.Call = Make.Call(Make.Name(decoratorCallable), list_astKeywords=listDecoratorKeywords)
151
+
152
+ ingredientsFunction.astFunctionDef.decorator_list = [astDecorator]
153
+ return ingredientsFunction
154
+
155
+ @dataclasses.dataclass
156
+ class SpicesJobNumba:
157
+ useNumbaProgressBar: bool = True
158
+ numbaProgressBarIdentifier: ast_Identifier = 'ProgressBarGroupsOfFolds'
159
+ parametersNumba = parametersNumbaDefault
160
+
161
+ @dataclasses.dataclass
162
+ class RecipeJob:
163
+ state: ComputationState
164
+ # TODO create function to calculate `foldsTotalEstimated`
165
+ foldsTotalEstimated: int = 0
166
+ shatteredDataclass: ShatteredDataclass = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
167
+
168
+ # ========================================
169
+ # Source
170
+ source_astModule = parsePathFilename2astModule(theNumbaFlow.pathFilenameSequential)
171
+ sourceCountCallable: ast_Identifier = theNumbaFlow.callableSequential
172
+
173
+ sourceLogicalPathModuleDataclass: str_nameDOTname = theNumbaFlow.logicalPathModuleDataclass
174
+ sourceDataclassIdentifier: ast_Identifier = theNumbaFlow.dataclassIdentifier
175
+ sourceDataclassInstance: ast_Identifier = theNumbaFlow.dataclassInstance
176
+
177
+ sourcePathPackage: PurePosixPath | None = theNumbaFlow.pathPackage
178
+ sourcePackageIdentifier: ast_Identifier | None = theNumbaFlow.packageIdentifier
179
+
180
+ # ========================================
181
+ # Filesystem (names of physical objects)
182
+ pathPackage: PurePosixPath | None = None
183
+ pathModule: PurePosixPath | None = PurePosixPath(getPathRootJobDEFAULT())
184
+ """ `pathModule` will override `pathPackage` and `logicalPathRoot`."""
185
+ fileExtension: str = theNumbaFlow.fileExtension
186
+ pathFilenameFoldsTotal: PurePosixPath = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
187
+
188
+ # ========================================
189
+ # Logical identifiers (as opposed to physical identifiers)
190
+ # ========================================
191
+ packageIdentifier: ast_Identifier | None = None
192
+ logicalPathRoot: str_nameDOTname | None = None
193
+ """ `logicalPathRoot` likely corresponds to a physical filesystem directory."""
194
+ moduleIdentifier: ast_Identifier = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
195
+ countCallable: ast_Identifier = sourceCountCallable
196
+ dataclassIdentifier: ast_Identifier | None = sourceDataclassIdentifier
197
+ dataclassInstance: ast_Identifier | None = sourceDataclassInstance
198
+ logicalPathModuleDataclass: str_nameDOTname | None = sourceLogicalPathModuleDataclass
199
+
200
+ # ========================================
201
+ # Datatypes
202
+ DatatypeFoldsTotal: TypeAlias = TheDatatypeFoldsTotal
203
+ DatatypeElephino: TypeAlias = TheDatatypeElephino
204
+ DatatypeLeavesTotal: TypeAlias = TheDatatypeLeavesTotal
205
+
206
+ def _makePathFilename(self,
207
+ pathRoot: PurePosixPath | None = None,
208
+ logicalPathINFIX: str_nameDOTname | None = None,
209
+ filenameStem: str | None = None,
210
+ fileExtension: str | None = None,
211
+ ) -> PurePosixPath:
212
+ if pathRoot is None:
213
+ pathRoot = self.pathPackage or PurePosixPath(Path.cwd())
214
+ if logicalPathINFIX:
215
+ whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
216
+ pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
217
+ if filenameStem is None:
218
+ filenameStem = self.moduleIdentifier
219
+ if fileExtension is None:
220
+ fileExtension = self.fileExtension
221
+ filename: str = filenameStem + fileExtension
222
+ return pathRoot.joinpath(filename)
223
+
224
+ @property
225
+ def pathFilenameModule(self) -> PurePosixPath:
226
+ if self.pathModule is None:
227
+ return self._makePathFilename()
228
+ else:
229
+ return self._makePathFilename(pathRoot=self.pathModule, logicalPathINFIX=None)
230
+
231
+ def __post_init__(self):
232
+ pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(self.state.mapShape))
233
+
234
+ if self.moduleIdentifier is None: # type: ignore
235
+ self.moduleIdentifier = pathFilenameFoldsTotal.stem
236
+
237
+ if self.pathFilenameFoldsTotal is None: # type: ignore
238
+ self.pathFilenameFoldsTotal = pathFilenameFoldsTotal
239
+
240
+ if self.shatteredDataclass is None and self.logicalPathModuleDataclass and self.dataclassIdentifier and self.dataclassInstance: # type: ignore
241
+ self.shatteredDataclass = shatter_dataclassesDOTdataclass(self.logicalPathModuleDataclass, self.dataclassIdentifier, self.dataclassInstance)
242
+
243
+ # ========================================
244
+ # Fields you probably don't need =================================
245
+ # Dispatcher =================================
246
+ sourceDispatcherCallable: ast_Identifier = theNumbaFlow.callableDispatcher
247
+ dispatcherCallable: ast_Identifier = sourceDispatcherCallable
248
+ # Parallel counting =================================
249
+ sourceDataclassInstanceTaskDistribution: ast_Identifier = theNumbaFlow.dataclassInstanceTaskDistribution
250
+ sourceConcurrencyManagerNamespace: ast_Identifier = theNumbaFlow.concurrencyManagerNamespace
251
+ sourceConcurrencyManagerIdentifier: ast_Identifier = theNumbaFlow.concurrencyManagerIdentifier
252
+ dataclassInstanceTaskDistribution: ast_Identifier = sourceDataclassInstanceTaskDistribution
253
+ concurrencyManagerNamespace: ast_Identifier = sourceConcurrencyManagerNamespace
254
+ concurrencyManagerIdentifier: ast_Identifier = sourceConcurrencyManagerIdentifier
255
+
256
+ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
257
+ # TODO a tool to automatically remove unused variables from the ArgumentsSpecification (return, and returns) _might_ be nice.
258
+ # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
259
+
260
+ listAllIngredientsFunctions = [
261
+ (ingredientsInitialize := astModuleToIngredientsFunction(numbaFlow.source_astModule, numbaFlow.sourceCallableInitialize)),
262
+ (ingredientsParallel := astModuleToIngredientsFunction(numbaFlow.source_astModule, numbaFlow.sourceCallableParallel)),
263
+ (ingredientsSequential := astModuleToIngredientsFunction(numbaFlow.source_astModule, numbaFlow.sourceCallableSequential)),
264
+ (ingredientsDispatcher := astModuleToIngredientsFunction(numbaFlow.source_astModule, numbaFlow.sourceCallableDispatcher)),
265
+ ]
266
+
267
+ # Inline functions ========================================================
268
+ dictionaryReplacementStatements = Z0Z_makeDictionaryReplacementStatements(numbaFlow.source_astModule)
269
+ # NOTE Replacements statements are based on the identifiers in the _source_, so operate on the source identifiers.
270
+ ingredientsInitialize.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsInitialize.astFunctionDef, dictionaryReplacementStatements)
271
+ ingredientsParallel.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsParallel.astFunctionDef, dictionaryReplacementStatements)
272
+ ingredientsSequential.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsSequential.astFunctionDef, dictionaryReplacementStatements)
273
+
274
+ # assignRecipeIdentifiersToCallable. =============================
275
+ # TODO How can I use `RecipeSynthesizeFlow` as the SSOT for the pairs of items that may need to be replaced?
276
+ # NOTE reminder: you are updating these `ast.Name` here (and not in a more general search) because this is a
277
+ # narrow search for `ast.Call` so you won't accidentally replace unrelated `ast.Name`.
278
+ listFindReplace = [(numbaFlow.sourceCallableDispatcher, numbaFlow.callableDispatcher),
279
+ (numbaFlow.sourceCallableInitialize, numbaFlow.callableInitialize),
280
+ (numbaFlow.sourceCallableParallel, numbaFlow.callableParallel),
281
+ (numbaFlow.sourceCallableSequential, numbaFlow.callableSequential),]
282
+ for ingredients in listAllIngredientsFunctions:
283
+ for source_Identifier, recipe_Identifier in listFindReplace:
284
+ updateCallName = NodeChanger(ifThis.isCall_Identifier(source_Identifier), Then.DOTfunc(Then.replaceWith(Make.Name(recipe_Identifier))))
285
+ updateCallName.visit(ingredients.astFunctionDef)
286
+
287
+ ingredientsDispatcher.astFunctionDef.name = numbaFlow.callableDispatcher
288
+ ingredientsInitialize.astFunctionDef.name = numbaFlow.callableInitialize
289
+ ingredientsParallel.astFunctionDef.name = numbaFlow.callableParallel
290
+ ingredientsSequential.astFunctionDef.name = numbaFlow.callableSequential
291
+
292
+ # Assign identifiers per the recipe. ==============================
293
+ listFindReplace = [(numbaFlow.sourceDataclassInstance, numbaFlow.dataclassInstance),
294
+ (numbaFlow.sourceDataclassInstanceTaskDistribution, numbaFlow.dataclassInstanceTaskDistribution),
295
+ (numbaFlow.sourceConcurrencyManagerNamespace, numbaFlow.concurrencyManagerNamespace),]
296
+ for ingredients in listAllIngredientsFunctions:
297
+ for source_Identifier, recipe_Identifier in listFindReplace:
298
+ updateName = NodeChanger(ifThis.isName_Identifier(source_Identifier), Then.DOTid(Then.replaceWith(recipe_Identifier)))
299
+ update_arg = NodeChanger(ifThis.isArgument_Identifier(source_Identifier), Then.DOTarg(Then.replaceWith(recipe_Identifier)))
300
+ updateName.visit(ingredients.astFunctionDef)
301
+ update_arg.visit(ingredients.astFunctionDef)
302
+
303
+ updateConcurrencyManager = NodeChanger(ifThis.isCallAttributeNamespace_Identifier(numbaFlow.sourceConcurrencyManagerNamespace, numbaFlow.sourceConcurrencyManagerIdentifier)
304
+ , Then.DOTfunc(Then.replaceWith(Make.Attribute(Make.Name(numbaFlow.concurrencyManagerNamespace), numbaFlow.concurrencyManagerIdentifier))))
305
+ updateConcurrencyManager.visit(ingredientsDispatcher.astFunctionDef)
306
+
307
+ # shatter Dataclass =======================================================
308
+ instance_Identifier = numbaFlow.dataclassInstance
309
+ getTheOtherRecord_damn = numbaFlow.dataclassInstanceTaskDistribution
310
+ shatteredDataclass = shatter_dataclassesDOTdataclass(numbaFlow.logicalPathModuleDataclass, numbaFlow.sourceDataclassIdentifier, instance_Identifier)
311
+ ingredientsDispatcher.imports.update(shatteredDataclass.ledger)
312
+
313
+ # Change callable parameters and Call to the callable at the same time ====
314
+ # TODO How can I use ast and/or other tools to ensure that when I change a callable, I also change the statements that call the callable?
315
+ # Asked differently, how do I integrate separate statements into a "subroutine", and that subroutine is "atomic/indivisible"?
316
+ # sequentialCallable =========================================================
317
+ ingredientsSequential.astFunctionDef.args = Make.argumentsSpecification(args=shatteredDataclass.list_argAnnotated4ArgumentsSpecification)
318
+ astCallSequentialCallable = Make.Call(Make.Name(numbaFlow.callableSequential), shatteredDataclass.listName4Parameters)
319
+ changeReturnSequentialCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(shatteredDataclass.fragments4AssignmentOrParameters)))
320
+ ingredientsSequential.astFunctionDef.returns = shatteredDataclass.signatureReturnAnnotation
321
+ replaceAssignSequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.replaceWith(Make.Assign(listTargets=[shatteredDataclass.fragments4AssignmentOrParameters], value=astCallSequentialCallable)))
322
+
323
+ unpack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.insertThisAbove(shatteredDataclass.listUnpack))
324
+ repack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.insertThisBelow([shatteredDataclass.repack]))
325
+
326
+ changeReturnSequentialCallable.visit(ingredientsSequential.astFunctionDef)
327
+ replaceAssignSequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
328
+ unpack4sequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
329
+ repack4sequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
330
+
331
+ ingredientsSequential.astFunctionDef = Z0Z_lameFindReplace(ingredientsSequential.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name) # type: ignore
332
+
333
+ # parallelCallable =========================================================
334
+ ingredientsParallel.astFunctionDef.args = Make.argumentsSpecification(args=shatteredDataclass.list_argAnnotated4ArgumentsSpecification)
335
+ replaceCall2concurrencyManager = NodeChanger(ifThis.isCallAttributeNamespace_Identifier(numbaFlow.concurrencyManagerNamespace, numbaFlow.concurrencyManagerIdentifier), Then.replaceWith(Make.Call(Make.Attribute(Make.Name(numbaFlow.concurrencyManagerNamespace), numbaFlow.concurrencyManagerIdentifier), listArguments=[Make.Name(numbaFlow.callableParallel)] + shatteredDataclass.listName4Parameters)))
336
+
337
+ # NOTE I am dissatisfied with this logic for many reasons, including that it requires separate NodeCollector and NodeReplacer instances.
338
+ astCallConcurrencyResult: list[ast.Call] = []
339
+ get_astCallConcurrencyResult: NodeTourist = NodeTourist(ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(getTheOtherRecord_damn)), lambda node: NodeTourist(be.Call, Then.appendTo(astCallConcurrencyResult)).visit(node)) # pyright: ignore[reportUnknownArgumentType, reportUnknownLambdaType]
340
+ get_astCallConcurrencyResult.visit(ingredientsDispatcher.astFunctionDef)
341
+ replaceAssignParallelCallable = NodeChanger(ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(getTheOtherRecord_damn)), Then.DOTvalue(Then.replaceWith(astCallConcurrencyResult[0])))
342
+ replaceAssignParallelCallable.visit(ingredientsDispatcher.astFunctionDef)
343
+ changeReturnParallelCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(shatteredDataclass.countingVariableName)))
344
+ ingredientsParallel.astFunctionDef.returns = shatteredDataclass.countingVariableAnnotation
345
+
346
+ unpack4parallelCallable = NodeChanger(ifThis.isAssignAndValueIsCallAttributeNamespace_Identifier(numbaFlow.concurrencyManagerNamespace, numbaFlow.concurrencyManagerIdentifier), Then.insertThisAbove(shatteredDataclass.listUnpack))
347
+
348
+ unpack4parallelCallable.visit(ingredientsDispatcher.astFunctionDef)
349
+ replaceCall2concurrencyManager.visit(ingredientsDispatcher.astFunctionDef)
350
+ changeReturnParallelCallable.visit(ingredientsParallel.astFunctionDef)
351
+
352
+ ingredientsParallel.astFunctionDef = Z0Z_lameFindReplace(ingredientsParallel.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name) # type: ignore
353
+
354
+ # numba decorators =========================================
355
+ ingredientsParallel = decorateCallableWithNumba(ingredientsParallel)
356
+ ingredientsSequential = decorateCallableWithNumba(ingredientsSequential)
357
+
358
+ # Module-level transformations ===========================================================
359
+ ingredientsModuleNumbaUnified = IngredientsModule(ingredientsFunction=listAllIngredientsFunctions, imports=LedgerOfImports(numbaFlow.source_astModule))
360
+
361
+ write_astModule(ingredientsModuleNumbaUnified, numbaFlow.pathFilenameDispatcher, numbaFlow.packageIdentifier)
362
+
363
+ if __name__ == '__main__':
364
+ makeNumbaFlow(theNumbaFlow)