mapFolding 0.8.5__py3-none-any.whl → 0.9.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 (33) hide show
  1. mapFolding/__init__.py +66 -18
  2. mapFolding/basecamp.py +32 -17
  3. mapFolding/beDRY.py +3 -3
  4. mapFolding/oeis.py +121 -25
  5. mapFolding/someAssemblyRequired/__init__.py +48 -27
  6. mapFolding/someAssemblyRequired/_theTypes.py +11 -15
  7. mapFolding/someAssemblyRequired/_tool_Make.py +40 -12
  8. mapFolding/someAssemblyRequired/_tool_Then.py +59 -25
  9. mapFolding/someAssemblyRequired/_toolboxAntecedents.py +151 -276
  10. mapFolding/someAssemblyRequired/_toolboxContainers.py +185 -51
  11. mapFolding/someAssemblyRequired/_toolboxPython.py +165 -44
  12. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +141 -20
  13. mapFolding/someAssemblyRequired/toolboxNumba.py +93 -52
  14. mapFolding/someAssemblyRequired/transformationTools.py +228 -138
  15. mapFolding/syntheticModules/numbaCount_doTheNeedful.py +0 -1
  16. mapFolding/theSSOT.py +147 -55
  17. mapFolding/toolboxFilesystem.py +1 -1
  18. mapfolding-0.9.0.dist-info/METADATA +177 -0
  19. mapfolding-0.9.0.dist-info/RECORD +46 -0
  20. tests/__init__.py +44 -0
  21. tests/conftest.py +75 -7
  22. tests/test_computations.py +90 -9
  23. tests/test_filesystem.py +32 -33
  24. tests/test_other.py +0 -1
  25. tests/test_tasks.py +2 -2
  26. mapFolding/noHomeYet.py +0 -32
  27. mapFolding/someAssemblyRequired/newInliner.py +0 -22
  28. mapfolding-0.8.5.dist-info/METADATA +0 -190
  29. mapfolding-0.8.5.dist-info/RECORD +0 -48
  30. {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/WHEEL +0 -0
  31. {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/entry_points.txt +0 -0
  32. {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/licenses/LICENSE +0 -0
  33. {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,35 @@
1
- """Synthesize one file to compute `foldsTotal` of `mapShape`."""
1
+ """
2
+ Job-specific Numba Code Generation for Map Folding Calculations
3
+
4
+ This module specializes in generating highly-optimized, single-purpose Numba modules
5
+ for specific map folding calculation jobs. Unlike the general-purpose transformation
6
+ in toolboxNumba.py, this module creates standalone Python modules optimized for a
7
+ single map shape with statically-encoded parameters.
8
+
9
+ The code generation pipeline focuses on:
10
+
11
+ 1. Converting function parameters to initialized variables with concrete values.
12
+ 2. Replacing dynamic computations with statically-known values.
13
+ 3. Eliminating unused code paths and variables.
14
+ 4. Adding progress tracking for long-running calculations.
15
+ 5. Applying appropriate Numba optimizations for the specific calculation.
16
+
17
+ This creates extremely fast, specialized implementations that can be run directly
18
+ as Python scripts or further compiled into standalone executables.
19
+ """
20
+
2
21
  from mapFolding.toolboxFilesystem import getPathFilenameFoldsTotal
3
- from mapFolding.someAssemblyRequired import ast_Identifier, be, ifThis, Make, NodeChanger, Then, IngredientsFunction, IngredientsModule
22
+ from mapFolding.someAssemblyRequired import ast_Identifier, ifThis, Make, NodeChanger, Then, IngredientsFunction, IngredientsModule, LedgerOfImports
4
23
  from mapFolding.someAssemblyRequired.toolboxNumba import RecipeJob, SpicesJobNumba, decorateCallableWithNumba
5
- from mapFolding.someAssemblyRequired.transformationTools import astModuleToIngredientsFunction, write_astModule
24
+ from mapFolding.someAssemblyRequired.transformationTools import extractFunctionDef, write_astModule
6
25
  from mapFolding.someAssemblyRequired.transformationTools import makeInitializedComputationState
7
- from mapFolding.theSSOT import The
26
+ from mapFolding.theSSOT import The, raiseIfNoneGitHubIssueNumber3
27
+ from mapFolding.oeis import getFoldsTotalKnown
8
28
  from typing import cast
9
29
  from Z0Z_tools import autoDecodingRLE
10
30
  from pathlib import PurePosixPath
11
31
  import ast
32
+ """Synthesize one file to compute `foldsTotal` of `mapShape`."""
12
33
 
13
34
  list_IdentifiersNotUsedAllHARDCODED = ['concurrencyLimit', 'foldsTotal', 'mapShape',]
14
35
  list_IdentifiersNotUsedParallelSequentialHARDCODED = ['indexLeaf']
@@ -21,13 +42,36 @@ list_IdentifiersStaticValuesHARDCODED = ['dimensionsTotal', 'leavesTotal',]
21
42
  list_IdentifiersNotUsedHARDCODED = list_IdentifiersStaticValuesHARDCODED + list_IdentifiersReplacedHARDCODED + list_IdentifiersNotUsedAllHARDCODED + list_IdentifiersNotUsedParallelSequentialHARDCODED + list_IdentifiersNotUsedSequentialHARDCODED
22
43
 
23
44
  def addLauncherNumbaProgress(ingredientsModule: IngredientsModule, ingredientsFunction: IngredientsFunction, job: RecipeJob, spices: SpicesJobNumba) -> tuple[IngredientsModule, IngredientsFunction]:
45
+ """
46
+ Add progress tracking capabilities to a Numba-optimized function.
47
+
48
+ This function modifies both the module and the function to integrate Numba-compatible
49
+ progress tracking for long-running calculations. It performs several key transformations:
50
+
51
+ 1. Adds a progress bar parameter to the function signature
52
+ 2. Replaces counting increments with progress bar updates
53
+ 3. Creates a launcher section that displays and updates progress
54
+ 4. Configures file output to save results upon completion
24
55
 
56
+ The progress tracking is particularly important for map folding calculations
57
+ which can take hours or days to complete, providing visual feedback and
58
+ estimated completion times.
59
+
60
+ Parameters:
61
+ ingredientsModule: The module where the function is defined.
62
+ ingredientsFunction: The function to modify with progress tracking.
63
+ job: Configuration specifying shape details and output paths.
64
+ spices: Configuration specifying progress bar details.
65
+
66
+ Returns:
67
+ A tuple containing the modified module and function with progress tracking.
68
+ """
25
69
  linesLaunch: str = f"""
26
70
  if __name__ == '__main__':
27
71
  with ProgressBar(total={job.foldsTotalEstimated}, update_interval=2) as statusUpdate:
28
72
  {job.countCallable}(statusUpdate)
29
73
  foldsTotal = statusUpdate.n * {job.state.leavesTotal}
30
- print('map {job.state.mapShape} =', foldsTotal)
74
+ print('\\nmap {job.state.mapShape} =', foldsTotal)
31
75
  writeStream = open('{job.pathFilenameFoldsTotal.as_posix()}', 'w')
32
76
  writeStream.write(str(foldsTotal))
33
77
  writeStream.close()
@@ -50,12 +94,35 @@ if __name__ == '__main__':
50
94
  return ingredientsModule, ingredientsFunction
51
95
 
52
96
  def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: IngredientsFunction, job: RecipeJob) -> IngredientsFunction:
97
+ """
98
+ Convert function parameters into initialized variables with concrete values.
99
+
100
+ This function implements a critical transformation that converts function parameters
101
+ into statically initialized variables in the function body. This enables several
102
+ optimizations:
103
+
104
+ 1. Eliminating parameter passing overhead.
105
+ 2. Embedding concrete values directly in the code.
106
+ 3. Allowing Numba to optimize based on known value characteristics.
107
+ 4. Simplifying function signatures for specialized use cases.
108
+
109
+ The function handles different data types (scalars, arrays, custom types) appropriately,
110
+ replacing abstract parameter references with concrete values from the computation state.
111
+ It also removes unused parameters and variables to eliminate dead code.
112
+
113
+ Parameters:
114
+ ingredientsFunction: The function to transform.
115
+ job: Recipe containing concrete values for parameters and field metadata.
116
+
117
+ Returns:
118
+ The modified function with parameters converted to initialized variables.
119
+ """
53
120
  ingredientsFunction.imports.update(job.shatteredDataclass.ledger)
54
121
 
55
122
  list_IdentifiersNotUsed = list_IdentifiersNotUsedHARDCODED
56
123
 
57
- list_argCauseMyBrainRefusesToDoThisTheRightWay = ingredientsFunction.astFunctionDef.args.args + ingredientsFunction.astFunctionDef.args.posonlyargs + ingredientsFunction.astFunctionDef.args.kwonlyargs
58
- for ast_arg in list_argCauseMyBrainRefusesToDoThisTheRightWay:
124
+ list_argCuzMyBrainRefusesToThink = ingredientsFunction.astFunctionDef.args.args + ingredientsFunction.astFunctionDef.args.posonlyargs + ingredientsFunction.astFunctionDef.args.kwonlyargs
125
+ for ast_arg in list_argCuzMyBrainRefusesToThink:
59
126
  if ast_arg.arg in job.shatteredDataclass.field2AnnAssign:
60
127
  if ast_arg.arg in list_IdentifiersNotUsed:
61
128
  pass
@@ -63,12 +130,11 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
63
130
  ImaAnnAssign, elementConstructor = job.shatteredDataclass.Z0Z_field2AnnAssign[ast_arg.arg]
64
131
  match elementConstructor:
65
132
  case 'scalar':
66
- ImaAnnAssign.value.args[0].value = int(job.state.__dict__[ast_arg.arg]) # type: ignore
133
+ ImaAnnAssign.value.args[0].value = int(job.state.__dict__[ast_arg.arg]) # type: ignore
67
134
  case 'array':
68
- # print(ast.dump(ImaAnnAssign))
69
135
  dataAsStrRLE: str = autoDecodingRLE(job.state.__dict__[ast_arg.arg], addSpaces=True)
70
136
  dataAs_astExpr: ast.expr = cast(ast.Expr, ast.parse(dataAsStrRLE).body[0]).value
71
- ImaAnnAssign.value.args = [dataAs_astExpr] # type: ignore
137
+ ImaAnnAssign.value.args = [dataAs_astExpr] # type: ignore
72
138
  case _:
73
139
  list_exprDOTannotation: list[ast.expr] = []
74
140
  list_exprDOTvalue: list[ast.expr] = []
@@ -87,12 +153,36 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
87
153
  ast.fix_missing_locations(ingredientsFunction.astFunctionDef)
88
154
  return ingredientsFunction
89
155
 
90
- def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba):
91
- # get the raw ingredients: data and the algorithm
92
- ingredientsCount: IngredientsFunction = astModuleToIngredientsFunction(job.source_astModule, job.countCallable)
156
+ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
157
+ """
158
+ Generate a highly-optimized, single-purpose Numba module for a specific map shape.
159
+
160
+ This function implements the complete transformation pipeline for creating a
161
+ standalone, specialized implementation for calculating map folding solutions for
162
+ a specific shape. The process includes:
163
+
164
+ 1. Extracting the counting function from the source module
165
+ 2. Removing unused code paths based on static analysis
166
+ 3. Replacing dynamic variables with concrete values
167
+ 4. Converting parameters to initialized variables
168
+ 5. Adding progress tracking if requested
169
+ 6. Applying Numba optimizations and type specifications
170
+ 7. Writing the final module to the filesystem
171
+
172
+ The resulting Python module is both human-readable and extraordinarily efficient,
173
+ with all shape-specific optimizations statically encoded. This creates specialized
174
+ implementations that can be orders of magnitude faster than general-purpose code.
175
+
176
+ Parameters:
177
+ job: Configuration specifying the target shape, paths, and computation state.
178
+ spices: Configuration specifying Numba and progress tracking options.
179
+ """
180
+ astFunctionDef = extractFunctionDef(job.source_astModule, job.countCallable)
181
+ if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
182
+ ingredientsCount: IngredientsFunction = IngredientsFunction(astFunctionDef, LedgerOfImports())
93
183
 
94
184
  # Change the return so you can dynamically determine which variables are not used
95
- removeReturnStatement = NodeChanger(be.Return, Then.removeIt)
185
+ removeReturnStatement = NodeChanger(lambda node: isinstance(node, ast.Return), Then.removeIt) # type: ignore
96
186
  removeReturnStatement.visit(ingredientsCount.astFunctionDef)
97
187
  ingredientsCount.astFunctionDef.returns = Make.Constant(value=None)
98
188
 
@@ -110,21 +200,51 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba):
110
200
  NodeChanger(findThis, doThat).visit(ingredientsCount.astFunctionDef)
111
201
 
112
202
  # This launcher eliminates the use of one identifier, so run it now and you can dynamically determine which variables are not used
203
+ ingredientsModule = IngredientsModule()
113
204
  if spices.useNumbaProgressBar:
114
- ingredientsModule = IngredientsModule()
115
205
  ingredientsModule, ingredientsCount = addLauncherNumbaProgress(ingredientsModule, ingredientsCount, job, spices)
116
206
  spices.parametersNumba['nogil'] = True
117
207
 
118
208
  ingredientsCount = move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsCount, job)
119
209
 
210
+ Z0Z_Identifier = 'DatatypeLeavesTotal'
211
+ Z0Z_type = 'uint8'
212
+ ingredientsModule.imports.addImportFrom_asStr('numba', Z0Z_type)
213
+ Z0Z_module = 'typing'
214
+ Z0Z_annotation = 'TypeAlias'
215
+ ingredientsModule.imports.addImportFrom_asStr(Z0Z_module, Z0Z_annotation)
216
+ Z0Z_statement = Make.AnnAssign(Make.Name(Z0Z_Identifier, ast.Store()), Make.Name(Z0Z_annotation), Make.Name(Z0Z_type))
217
+ ingredientsModule.appendPrologue(statement=Z0Z_statement)
218
+
219
+ Z0Z_Identifier = 'DatatypeElephino'
220
+ Z0Z_type = 'int16'
221
+ ingredientsModule.imports.addImportFrom_asStr('numba', Z0Z_type)
222
+ Z0Z_module = 'typing'
223
+ Z0Z_annotation = 'TypeAlias'
224
+ ingredientsModule.imports.addImportFrom_asStr(Z0Z_module, Z0Z_annotation)
225
+ Z0Z_statement = Make.AnnAssign(Make.Name(Z0Z_Identifier, ast.Store()), Make.Name(Z0Z_annotation), Make.Name(Z0Z_type))
226
+ ingredientsModule.appendPrologue(statement=Z0Z_statement)
227
+
228
+ ingredientsCount.imports.removeImportFromModule('mapFolding.theSSOT')
229
+ Z0Z_module = 'numpy'
230
+ Z0Z_asname = 'Array1DLeavesTotal'
231
+ ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
232
+ Z0Z_type_name = 'uint8'
233
+ ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
234
+ Z0Z_asname = 'Array1DElephino'
235
+ ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
236
+ Z0Z_type_name = 'int16'
237
+ ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
238
+ Z0Z_asname = 'Array3D'
239
+ ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
240
+ Z0Z_type_name = 'uint8'
241
+ ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
242
+
120
243
  ingredientsCount.astFunctionDef.decorator_list = [] # TODO low-priority, handle this more elegantly
121
244
  # TODO when I add the function signature in numba style back to the decorator, the logic needs to handle `ProgressBarType:`
122
245
  ingredientsCount = decorateCallableWithNumba(ingredientsCount, spices.parametersNumba)
123
246
 
124
247
  ingredientsModule.appendIngredientsFunction(ingredientsCount)
125
-
126
- # add imports, make str, remove unused imports
127
- # put on disk
128
248
  write_astModule(ingredientsModule, job.pathFilenameModule, job.packageIdentifier)
129
249
 
130
250
  """
@@ -149,10 +269,11 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba):
149
269
  """
150
270
 
151
271
  if __name__ == '__main__':
152
- mapShape = (3,4)
272
+ mapShape = (2,4)
153
273
  state = makeInitializedComputationState(mapShape)
274
+ foldsTotalEstimated = getFoldsTotalKnown(state.mapShape) // state.leavesTotal
154
275
  pathModule = PurePosixPath(The.pathPackage, 'jobs')
155
276
  pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(state.mapShape, pathModule))
156
- aJob = RecipeJob(state, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
277
+ aJob = RecipeJob(state, foldsTotalEstimated, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
157
278
  spices = SpicesJobNumba()
158
279
  makeJobNumba(aJob, spices)
@@ -1,45 +1,42 @@
1
1
  """
2
- Numba-specific ingredients for optimized code generation.
2
+ Numba-specific Tools for Generating Optimized Code
3
3
 
4
- This module provides specialized tools, constants, and types specifically designed
5
- for transforming Python code into Numba-accelerated implementations. It implements:
4
+ This module provides specialized tools for transforming standard Python code into
5
+ Numba-accelerated implementations. It implements a comprehensive transformation
6
+ assembly-line that:
6
7
 
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
8
+ 1. Converts dataclass-based algorithm implementations into Numba-compatible versions.
9
+ 2. Applies appropriate Numba decorators with optimized configuration settings.
10
+ 3. Restructures code to work within Numba's constraints.
11
+ 4. Manages type information for optimized compilation.
11
12
 
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.
13
+ The module bridges the gap between readable, maintainable Python code and
14
+ highly-optimized numerical computing implementations, enabling significant
15
+ performance improvements while preserving code semantics and correctness.
20
16
  """
21
17
 
22
18
  from collections.abc import Callable, Sequence
19
+ from mapFolding.someAssemblyRequired import grab, IngredientsModule, LedgerOfImports, Make, NodeChanger, NodeTourist, RecipeSynthesizeFlow, Then, ast_Identifier, ifThis, parsePathFilename2astModule, str_nameDOTname, IngredientsFunction, ShatteredDataclass
20
+ from mapFolding.someAssemblyRequired.transformationTools import inlineFunctionDef, Z0Z_lameFindReplace, astModuleToIngredientsFunction, shatter_dataclassesDOTdataclass, write_astModule
21
+ from mapFolding.theSSOT import ComputationState, DatatypeFoldsTotal as TheDatatypeFoldsTotal, DatatypeElephino as TheDatatypeElephino, DatatypeLeavesTotal as TheDatatypeLeavesTotal
23
22
  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
27
23
  from numba.core.compiler import CompilerBase as numbaCompilerBase
28
24
  from pathlib import Path, PurePosixPath
29
- from typing import Any, cast, Final, TYPE_CHECKING
25
+ from typing import Any, cast, Final, TYPE_CHECKING, TypeAlias, TypeGuard
30
26
  import ast
31
27
  import dataclasses
32
28
 
33
29
  try:
34
30
  from typing import NotRequired
35
31
  except Exception:
36
- from typing_extensions import NotRequired
32
+ from typing_extensions import NotRequired # pyright: ignore[reportShadowedImports]
37
33
 
38
34
  if TYPE_CHECKING:
39
35
  from typing import TypedDict
40
36
  else:
41
37
  TypedDict = dict[str,Any]
42
38
 
39
+ # Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
43
40
  theNumbaFlow: RecipeSynthesizeFlow = RecipeSynthesizeFlow()
44
41
 
45
42
  class ParametersNumba(TypedDict):
@@ -78,8 +75,8 @@ parametersNumbaSuperJitParallel: Final[ParametersNumba] = { **parametersNumbaSup
78
75
  """Speed, no helmet, concurrency, no talking to non-jitted functions."""
79
76
  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, }
80
77
 
81
- Z0Z_numbaDataTypeModule = 'numba'
82
- Z0Z_decoratorCallable = 'jit'
78
+ Z0Z_numbaDataTypeModule: str_nameDOTname = 'numba'
79
+ Z0Z_decoratorCallable: ast_Identifier = 'jit'
83
80
 
84
81
  def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, parametersNumba: ParametersNumba | None = None) -> IngredientsFunction:
85
82
  def Z0Z_UnhandledDecorators(astCallable: ast.FunctionDef) -> ast.FunctionDef:
@@ -90,7 +87,7 @@ def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, paramete
90
87
  warnings.warn(f"Removed decorator {ast.unparse(decoratorItem)} from {astCallable.name}")
91
88
  return astCallable
92
89
 
93
- def makeSpecialSignatureForNumba(signatureElement: ast.arg) -> ast.Subscript | ast.Name | None: # type: ignore
90
+ def makeSpecialSignatureForNumba(signatureElement: ast.arg) -> ast.Subscript | ast.Name | None: # pyright: ignore[reportUnusedFunction]
94
91
  if isinstance(signatureElement.annotation, ast.Subscript) and isinstance(signatureElement.annotation.slice, ast.Tuple):
95
92
  annotationShape: ast.expr = signatureElement.annotation.slice.elts[0]
96
93
  if isinstance(annotationShape, ast.Subscript) and isinstance(annotationShape.slice, ast.Tuple):
@@ -123,7 +120,6 @@ def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, paramete
123
120
 
124
121
  list_arg4signature_or_function: list[ast.expr] = []
125
122
  for parameter in ingredientsFunction.astFunctionDef.args.args:
126
- # Efficient translation of Python scalar types to Numba types https://github.com/hunterhogan/mapFolding/issues/8
127
123
  # For now, let Numba infer them.
128
124
  continue
129
125
  # signatureElement: ast.Subscript | ast.Name | None = makeSpecialSignatureForNumba(parameter)
@@ -142,8 +138,8 @@ def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, paramete
142
138
  parametersNumba = parametersNumbaDefault
143
139
  listDecoratorKeywords: list[ast.keyword] = [Make.keyword(parameterName, Make.Constant(parameterValue)) for parameterName, parameterValue in parametersNumba.items()]
144
140
 
145
- decoratorModule: str = Z0Z_numbaDataTypeModule
146
- decoratorCallable: str = Z0Z_decoratorCallable
141
+ decoratorModule = Z0Z_numbaDataTypeModule
142
+ decoratorCallable = Z0Z_decoratorCallable
147
143
  ingredientsFunction.imports.addImportFrom_asStr(decoratorModule, decoratorCallable)
148
144
  # Leave this line in so that global edits will change it.
149
145
  astDecorator: ast.Call = Make.Call(Make.Name(decoratorCallable), list_argsDecorator, listDecoratorKeywords)
@@ -152,18 +148,20 @@ def decorateCallableWithNumba(ingredientsFunction: IngredientsFunction, paramete
152
148
  ingredientsFunction.astFunctionDef.decorator_list = [astDecorator]
153
149
  return ingredientsFunction
154
150
 
151
+ # Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
155
152
  @dataclasses.dataclass
156
153
  class SpicesJobNumba:
157
154
  useNumbaProgressBar: bool = True
158
155
  numbaProgressBarIdentifier: ast_Identifier = 'ProgressBarGroupsOfFolds'
159
156
  parametersNumba = parametersNumbaDefault
160
157
 
158
+ # Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
161
159
  @dataclasses.dataclass
162
160
  class RecipeJob:
163
161
  state: ComputationState
164
162
  # TODO create function to calculate `foldsTotalEstimated`
165
163
  foldsTotalEstimated: int = 0
166
- shatteredDataclass: ShatteredDataclass = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
164
+ shatteredDataclass: ShatteredDataclass = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
167
165
 
168
166
  # ========================================
169
167
  # Source
@@ -183,20 +181,25 @@ class RecipeJob:
183
181
  pathModule: PurePosixPath | None = PurePosixPath(getPathRootJobDEFAULT())
184
182
  """ `pathModule` will override `pathPackage` and `logicalPathRoot`."""
185
183
  fileExtension: str = theNumbaFlow.fileExtension
186
- pathFilenameFoldsTotal: PurePosixPath = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
184
+ pathFilenameFoldsTotal: PurePosixPath = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
187
185
 
188
186
  # ========================================
189
187
  # Logical identifiers (as opposed to physical identifiers)
190
- # ========================================
191
188
  packageIdentifier: ast_Identifier | None = None
192
189
  logicalPathRoot: str_nameDOTname | None = None
193
190
  """ `logicalPathRoot` likely corresponds to a physical filesystem directory."""
194
- moduleIdentifier: ast_Identifier = dataclasses.field(default=None, init=True) # type: ignore[assignment, reportAssignmentType]
191
+ moduleIdentifier: ast_Identifier = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
195
192
  countCallable: ast_Identifier = sourceCountCallable
196
193
  dataclassIdentifier: ast_Identifier | None = sourceDataclassIdentifier
197
194
  dataclassInstance: ast_Identifier | None = sourceDataclassInstance
198
195
  logicalPathModuleDataclass: str_nameDOTname | None = sourceLogicalPathModuleDataclass
199
196
 
197
+ # ========================================
198
+ # Datatypes
199
+ DatatypeFoldsTotal: TypeAlias = TheDatatypeFoldsTotal
200
+ DatatypeElephino: TypeAlias = TheDatatypeElephino
201
+ DatatypeLeavesTotal: TypeAlias = TheDatatypeLeavesTotal
202
+
200
203
  def _makePathFilename(self,
201
204
  pathRoot: PurePosixPath | None = None,
202
205
  logicalPathINFIX: str_nameDOTname | None = None,
@@ -225,13 +228,13 @@ class RecipeJob:
225
228
  def __post_init__(self):
226
229
  pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(self.state.mapShape))
227
230
 
228
- if self.moduleIdentifier is None:
231
+ if self.moduleIdentifier is None: # pyright: ignore[reportUnnecessaryComparison]
229
232
  self.moduleIdentifier = pathFilenameFoldsTotal.stem
230
233
 
231
- if self.pathFilenameFoldsTotal is None:
234
+ if self.pathFilenameFoldsTotal is None: # pyright: ignore[reportUnnecessaryComparison]
232
235
  self.pathFilenameFoldsTotal = pathFilenameFoldsTotal
233
236
 
234
- if self.shatteredDataclass is None and self.logicalPathModuleDataclass and self.dataclassIdentifier and self.dataclassInstance:
237
+ if self.shatteredDataclass is None and self.logicalPathModuleDataclass and self.dataclassIdentifier and self.dataclassInstance: # pyright: ignore[reportUnnecessaryComparison]
235
238
  self.shatteredDataclass = shatter_dataclassesDOTdataclass(self.logicalPathModuleDataclass, self.dataclassIdentifier, self.dataclassInstance)
236
239
 
237
240
  # ========================================
@@ -247,7 +250,39 @@ class RecipeJob:
247
250
  concurrencyManagerNamespace: ast_Identifier = sourceConcurrencyManagerNamespace
248
251
  concurrencyManagerIdentifier: ast_Identifier = sourceConcurrencyManagerIdentifier
249
252
 
253
+ class be:
254
+ @staticmethod
255
+ def Call(node: ast.AST) -> TypeGuard[ast.Call]:
256
+ return isinstance(node, ast.Call)
257
+ @staticmethod
258
+ def Return(node: ast.AST) -> TypeGuard[ast.Return]:
259
+ return isinstance(node, ast.Return)
260
+
250
261
  def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
262
+ """
263
+ Transform standard Python algorithm code into optimized Numba implementations.
264
+
265
+ This function implements the complete transformation pipeline that converts
266
+ a conventional Python implementation into a high-performance Numba-accelerated
267
+ version. The process includes:
268
+
269
+ 1. Extracting core algorithm functions from the source module
270
+ 2. Inlining function calls to create self-contained implementations
271
+ 3. Transforming dataclass access patterns for Numba compatibility
272
+ 4. Applying appropriate Numba decorators with optimization settings
273
+ 5. Generating a unified module with sequential and parallel implementations
274
+ 6. Writing the transformed code to the filesystem with properly managed imports
275
+
276
+ The transformation preserves the logical structure and semantics of the original
277
+ implementation while making it compatible with Numba's constraints and
278
+ optimization capabilities. This creates a bridge between the general-purpose
279
+ implementation and the highly-optimized version needed for production use.
280
+
281
+ Parameters:
282
+ numbaFlow: Configuration object that specifies all aspects of the
283
+ transformation process, including source and target locations,
284
+ function and variable names, and output paths.
285
+ """
251
286
  # TODO a tool to automatically remove unused variables from the ArgumentsSpecification (return, and returns) _might_ be nice.
252
287
  # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
253
288
 
@@ -259,14 +294,14 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
259
294
  ]
260
295
 
261
296
  # Inline functions ========================================================
262
- dictionaryReplacementStatements = Z0Z_makeDictionaryReplacementStatements(numbaFlow.source_astModule)
263
297
  # NOTE Replacements statements are based on the identifiers in the _source_, so operate on the source identifiers.
264
- ingredientsInitialize.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsInitialize.astFunctionDef, dictionaryReplacementStatements)
265
- ingredientsParallel.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsParallel.astFunctionDef, dictionaryReplacementStatements)
266
- ingredientsSequential.astFunctionDef = Z0Z_inlineThisFunctionWithTheseValues(ingredientsSequential.astFunctionDef, dictionaryReplacementStatements)
298
+ ingredientsInitialize.astFunctionDef = inlineFunctionDef(numbaFlow.sourceCallableInitialize, numbaFlow.source_astModule)
299
+ ingredientsParallel.astFunctionDef = inlineFunctionDef(numbaFlow.sourceCallableParallel, numbaFlow.source_astModule)
300
+ ingredientsSequential.astFunctionDef = inlineFunctionDef(numbaFlow.sourceCallableSequential, numbaFlow.source_astModule)
267
301
 
268
302
  # assignRecipeIdentifiersToCallable. =============================
269
- # TODO How can I use `RecipeSynthesizeFlow` as the SSOT for the pairs of items that may need to be replaced?
303
+ # Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
304
+ # How can I use dataclass settings as the SSOT for specific actions? https://github.com/hunterhogan/mapFolding/issues/16
270
305
  # NOTE reminder: you are updating these `ast.Name` here (and not in a more general search) because this is a
271
306
  # narrow search for `ast.Call` so you won't accidentally replace unrelated `ast.Name`.
272
307
  listFindReplace = [(numbaFlow.sourceCallableDispatcher, numbaFlow.callableDispatcher),
@@ -275,7 +310,7 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
275
310
  (numbaFlow.sourceCallableSequential, numbaFlow.callableSequential),]
276
311
  for ingredients in listAllIngredientsFunctions:
277
312
  for source_Identifier, recipe_Identifier in listFindReplace:
278
- updateCallName = NodeChanger(ifThis.isCall_Identifier(source_Identifier), Then.DOTfunc(Then.replaceWith(Make.Name(recipe_Identifier))))
313
+ updateCallName = NodeChanger(ifThis.isCall_Identifier(source_Identifier), grab.funcAttribute(Then.replaceWith(Make.Name(recipe_Identifier))))
279
314
  updateCallName.visit(ingredients.astFunctionDef)
280
315
 
281
316
  ingredientsDispatcher.astFunctionDef.name = numbaFlow.callableDispatcher
@@ -289,13 +324,13 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
289
324
  (numbaFlow.sourceConcurrencyManagerNamespace, numbaFlow.concurrencyManagerNamespace),]
290
325
  for ingredients in listAllIngredientsFunctions:
291
326
  for source_Identifier, recipe_Identifier in listFindReplace:
292
- updateName = NodeChanger(ifThis.isName_Identifier(source_Identifier), Then.DOTid(Then.replaceWith(recipe_Identifier)))
293
- update_arg = NodeChanger(ifThis.isArgument_Identifier(source_Identifier), Then.DOTarg(Then.replaceWith(recipe_Identifier)))
327
+ updateName = NodeChanger(ifThis.isName_Identifier(source_Identifier) , grab.idAttribute(Then.replaceWith(recipe_Identifier)))
328
+ update_arg = NodeChanger(ifThis.isArgument_Identifier(source_Identifier), grab.argAttribute(Then.replaceWith(recipe_Identifier)))
294
329
  updateName.visit(ingredients.astFunctionDef)
295
330
  update_arg.visit(ingredients.astFunctionDef)
296
331
 
297
332
  updateConcurrencyManager = NodeChanger(ifThis.isCallAttributeNamespace_Identifier(numbaFlow.sourceConcurrencyManagerNamespace, numbaFlow.sourceConcurrencyManagerIdentifier)
298
- , Then.DOTfunc(Then.replaceWith(Make.Attribute(Make.Name(numbaFlow.concurrencyManagerNamespace), numbaFlow.concurrencyManagerIdentifier))))
333
+ , grab.funcAttribute(Then.replaceWith(Make.Attribute(Make.Name(numbaFlow.concurrencyManagerNamespace), numbaFlow.concurrencyManagerIdentifier))))
299
334
  updateConcurrencyManager.visit(ingredientsDispatcher.astFunctionDef)
300
335
 
301
336
  # shatter Dataclass =======================================================
@@ -304,25 +339,24 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
304
339
  shatteredDataclass = shatter_dataclassesDOTdataclass(numbaFlow.logicalPathModuleDataclass, numbaFlow.sourceDataclassIdentifier, instance_Identifier)
305
340
  ingredientsDispatcher.imports.update(shatteredDataclass.ledger)
306
341
 
342
+ # How can I use dataclass settings as the SSOT for specific actions? https://github.com/hunterhogan/mapFolding/issues/16
307
343
  # Change callable parameters and Call to the callable at the same time ====
308
- # 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?
309
- # Asked differently, how do I integrate separate statements into a "subroutine", and that subroutine is "atomic/indivisible"?
310
344
  # sequentialCallable =========================================================
311
345
  ingredientsSequential.astFunctionDef.args = Make.argumentsSpecification(args=shatteredDataclass.list_argAnnotated4ArgumentsSpecification)
312
346
  astCallSequentialCallable = Make.Call(Make.Name(numbaFlow.callableSequential), shatteredDataclass.listName4Parameters)
313
347
  changeReturnSequentialCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(shatteredDataclass.fragments4AssignmentOrParameters)))
314
348
  ingredientsSequential.astFunctionDef.returns = shatteredDataclass.signatureReturnAnnotation
315
- replaceAssignSequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.replaceWith(Make.Assign(listTargets=[shatteredDataclass.fragments4AssignmentOrParameters], value=astCallSequentialCallable)))
349
+ replaceAssignSequentialCallable = NodeChanger(ifThis.isAssignAndValueIs(ifThis.isCall_Identifier(numbaFlow.callableSequential)), Then.replaceWith(Make.Assign(listTargets=[shatteredDataclass.fragments4AssignmentOrParameters], value=astCallSequentialCallable)))
316
350
 
317
- unpack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.insertThisAbove(shatteredDataclass.listUnpack))
318
- repack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIsCall_Identifier(numbaFlow.callableSequential), Then.insertThisBelow([shatteredDataclass.repack]))
351
+ unpack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIs(ifThis.isCall_Identifier(numbaFlow.callableSequential)), Then.insertThisAbove(shatteredDataclass.listUnpack))
352
+ repack4sequentialCallable = NodeChanger(ifThis.isAssignAndValueIs(ifThis.isCall_Identifier(numbaFlow.callableSequential)), Then.insertThisBelow([shatteredDataclass.repack]))
319
353
 
320
354
  changeReturnSequentialCallable.visit(ingredientsSequential.astFunctionDef)
321
355
  replaceAssignSequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
322
356
  unpack4sequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
323
357
  repack4sequentialCallable.visit(ingredientsDispatcher.astFunctionDef)
324
358
 
325
- ingredientsSequential.astFunctionDef = Z0Z_lameFindReplace(ingredientsSequential.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name) # type: ignore
359
+ ingredientsSequential.astFunctionDef = Z0Z_lameFindReplace(ingredientsSequential.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name)
326
360
 
327
361
  # parallelCallable =========================================================
328
362
  ingredientsParallel.astFunctionDef.args = Make.argumentsSpecification(args=shatteredDataclass.list_argAnnotated4ArgumentsSpecification)
@@ -330,20 +364,20 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
330
364
 
331
365
  # NOTE I am dissatisfied with this logic for many reasons, including that it requires separate NodeCollector and NodeReplacer instances.
332
366
  astCallConcurrencyResult: list[ast.Call] = []
333
- 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]
367
+ get_astCallConcurrencyResult = NodeTourist(ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(getTheOtherRecord_damn)), getIt(astCallConcurrencyResult))
334
368
  get_astCallConcurrencyResult.visit(ingredientsDispatcher.astFunctionDef)
335
- replaceAssignParallelCallable = NodeChanger(ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(getTheOtherRecord_damn)), Then.DOTvalue(Then.replaceWith(astCallConcurrencyResult[0])))
369
+ replaceAssignParallelCallable = NodeChanger(ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier(getTheOtherRecord_damn)), grab.valueAttribute(Then.replaceWith(astCallConcurrencyResult[0])))
336
370
  replaceAssignParallelCallable.visit(ingredientsDispatcher.astFunctionDef)
337
371
  changeReturnParallelCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(shatteredDataclass.countingVariableName)))
338
372
  ingredientsParallel.astFunctionDef.returns = shatteredDataclass.countingVariableAnnotation
339
373
 
340
- unpack4parallelCallable = NodeChanger(ifThis.isAssignAndValueIsCallAttributeNamespace_Identifier(numbaFlow.concurrencyManagerNamespace, numbaFlow.concurrencyManagerIdentifier), Then.insertThisAbove(shatteredDataclass.listUnpack))
374
+ unpack4parallelCallable = NodeChanger(ifThis.isAssignAndValueIs(ifThis.isCallAttributeNamespace_Identifier(numbaFlow.concurrencyManagerNamespace, numbaFlow.concurrencyManagerIdentifier)), Then.insertThisAbove(shatteredDataclass.listUnpack))
341
375
 
342
376
  unpack4parallelCallable.visit(ingredientsDispatcher.astFunctionDef)
343
377
  replaceCall2concurrencyManager.visit(ingredientsDispatcher.astFunctionDef)
344
378
  changeReturnParallelCallable.visit(ingredientsParallel.astFunctionDef)
345
379
 
346
- ingredientsParallel.astFunctionDef = Z0Z_lameFindReplace(ingredientsParallel.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name) # type: ignore
380
+ ingredientsParallel.astFunctionDef = Z0Z_lameFindReplace(ingredientsParallel.astFunctionDef, shatteredDataclass.map_stateDOTfield2Name)
347
381
 
348
382
  # numba decorators =========================================
349
383
  ingredientsParallel = decorateCallableWithNumba(ingredientsParallel)
@@ -351,8 +385,15 @@ def makeNumbaFlow(numbaFlow: RecipeSynthesizeFlow) -> None:
351
385
 
352
386
  # Module-level transformations ===========================================================
353
387
  ingredientsModuleNumbaUnified = IngredientsModule(ingredientsFunction=listAllIngredientsFunctions, imports=LedgerOfImports(numbaFlow.source_astModule))
388
+ ingredientsModuleNumbaUnified.removeImportFromModule('numpy')
354
389
 
355
390
  write_astModule(ingredientsModuleNumbaUnified, numbaFlow.pathFilenameDispatcher, numbaFlow.packageIdentifier)
356
391
 
392
+ def getIt(astCallConcurrencyResult: list[ast.Call]) -> Callable[[ast.AST], ast.AST]:
393
+ def workhorse(node: ast.AST) -> ast.AST:
394
+ NodeTourist(be.Call, Then.appendTo(astCallConcurrencyResult)).visit(node)
395
+ return node
396
+ return workhorse
397
+
357
398
  if __name__ == '__main__':
358
399
  makeNumbaFlow(theNumbaFlow)