mapFolding 0.9.0__tar.gz → 0.9.1__tar.gz
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.
- {mapfolding-0.9.0 → mapfolding-0.9.1}/PKG-INFO +1 -1
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/beDRY.py +1 -2
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/oeis.py +20 -14
- mapfolding-0.9.1/mapFolding/someAssemblyRequired/RecipeJob.py +103 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/__init__.py +29 -29
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_tool_Make.py +1 -1
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_toolboxAntecedents.py +12 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_toolboxContainers.py +27 -27
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_toolboxPython.py +3 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +58 -26
- mapfolding-0.9.1/mapFolding/someAssemblyRequired/toolboxNumba.py +206 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/transformationTools.py +154 -7
- mapfolding-0.9.0/mapFolding/syntheticModules/numbaCount_doTheNeedful.py → mapfolding-0.9.1/mapFolding/syntheticModules/numbaCount.py +7 -4
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/theDao.py +19 -16
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/theSSOT.py +19 -9
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/PKG-INFO +1 -1
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/SOURCES.txt +2 -1
- {mapfolding-0.9.0 → mapfolding-0.9.1}/pyproject.toml +1 -1
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/test_computations.py +2 -1
- mapfolding-0.9.0/mapFolding/someAssemblyRequired/toolboxNumba.py +0 -399
- {mapfolding-0.9.0 → mapfolding-0.9.1}/LICENSE +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/README.md +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/__init__.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/basecamp.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/py.typed +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/__init__.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/flattened.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/hunterNumba.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/irvineJavaPort.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/jaxCount.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/jobsCompleted/[2x19]/p2x19.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/jobsCompleted/__init__.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/jobsCompleted/p2x19/p2x19.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/lunnanNumpy.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/lunnanWhile.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/rotatedEntryPoint.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/reference/total_countPlus1vsPlusN.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_theTypes.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_tool_Then.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/getLLVMforNoReason.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/syntheticModules/__init__.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/toolboxFilesystem.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/dependency_links.txt +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/entry_points.txt +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/requires.txt +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding.egg-info/top_level.txt +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/setup.cfg +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/__init__.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/conftest.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/test_filesystem.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/test_oeis.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/test_other.py +0 -0
- {mapfolding-0.9.0 → mapfolding-0.9.1}/tests/test_tasks.py +0 -0
|
@@ -331,8 +331,7 @@ def validateListDimensions(listDimensions: Sequence[int]) -> tuple[int, ...]:
|
|
|
331
331
|
ValueError
|
|
332
332
|
If the input is empty or contains negative values.
|
|
333
333
|
NotImplementedError
|
|
334
|
-
If fewer than two positive dimensions are provided
|
|
335
|
-
represent a valid map folding problem.
|
|
334
|
+
If fewer than two positive dimensions are provided.
|
|
336
335
|
"""
|
|
337
336
|
if not listDimensions:
|
|
338
337
|
raise ValueError("`listDimensions` is a required parameter.")
|
|
@@ -59,14 +59,20 @@ class SettingsOEIShardcodedValues(TypedDict):
|
|
|
59
59
|
valuesTestValidation: list[int]
|
|
60
60
|
|
|
61
61
|
settingsOEIShardcodedValues: dict[str, SettingsOEIShardcodedValues] = {
|
|
62
|
+
'A000136': {
|
|
63
|
+
'getMapShape': lambda n: tuple(sorted([1, n])),
|
|
64
|
+
'valuesBenchmark': [14],
|
|
65
|
+
'valuesTestParallelization': [*range(3, 7)],
|
|
66
|
+
'valuesTestValidation': [random.randint(2, 9)],
|
|
67
|
+
},
|
|
62
68
|
'A001415': {
|
|
63
|
-
'getMapShape': lambda n: (2, n)
|
|
69
|
+
'getMapShape': lambda n: tuple(sorted([2, n])),
|
|
64
70
|
'valuesBenchmark': [14],
|
|
65
71
|
'valuesTestParallelization': [*range(3, 7)],
|
|
66
72
|
'valuesTestValidation': [random.randint(2, 9)],
|
|
67
73
|
},
|
|
68
74
|
'A001416': {
|
|
69
|
-
'getMapShape': lambda n: (3, n)
|
|
75
|
+
'getMapShape': lambda n: tuple(sorted([3, n])),
|
|
70
76
|
'valuesBenchmark': [9],
|
|
71
77
|
'valuesTestParallelization': [*range(3, 5)],
|
|
72
78
|
'valuesTestValidation': [random.randint(2, 6)],
|
|
@@ -148,9 +154,9 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
|
|
|
148
154
|
sequence ID or if the content format is invalid.
|
|
149
155
|
"""
|
|
150
156
|
bFileLines: list[str] = OEISbFile.strip().splitlines()
|
|
151
|
-
if not bFileLines.pop(0).startswith(f"# {oeisID}"):
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
# if not bFileLines.pop(0).startswith(f"# {oeisID}"):
|
|
158
|
+
# warnings.warn(f"Content does not match sequence {oeisID}")
|
|
159
|
+
# return {-1: -1}
|
|
154
160
|
|
|
155
161
|
OEISsequence: dict[int, int] = {}
|
|
156
162
|
for line in bFileLines:
|
|
@@ -287,20 +293,20 @@ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
|
287
293
|
def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
|
|
288
294
|
"""
|
|
289
295
|
Construct the comprehensive settings dictionary for all implemented OEIS sequences.
|
|
290
|
-
|
|
291
|
-
This function builds a complete configuration dictionary for all supported OEIS
|
|
296
|
+
|
|
297
|
+
This function builds a complete configuration dictionary for all supported OEIS
|
|
292
298
|
sequences by retrieving and combining:
|
|
293
299
|
1. Sequence values from OEIS b-files
|
|
294
300
|
2. Sequence metadata (descriptions and offsets)
|
|
295
301
|
3. Hardcoded mapping functions and test values
|
|
296
|
-
|
|
297
|
-
The resulting dictionary provides a single authoritative source for all OEIS-related
|
|
302
|
+
|
|
303
|
+
The resulting dictionary provides a single authoritative source for all OEIS-related
|
|
298
304
|
configurations used throughout the package, including:
|
|
299
305
|
- Mathematical descriptions of each sequence
|
|
300
306
|
- Functions to convert between sequence indices and map dimensions
|
|
301
307
|
- Known sequence values retrieved from OEIS
|
|
302
308
|
- Testing and benchmarking reference values
|
|
303
|
-
|
|
309
|
+
|
|
304
310
|
Returns:
|
|
305
311
|
A dictionary mapping OEIS sequence IDs to their complete settings objects,
|
|
306
312
|
containing all metadata and known values needed for computation and validation.
|
|
@@ -341,18 +347,18 @@ def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
|
|
|
341
347
|
def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
|
|
342
348
|
"""
|
|
343
349
|
Retrieve the known total number of foldings for a given map shape.
|
|
344
|
-
|
|
350
|
+
|
|
345
351
|
This function looks up precalculated folding totals for specific map dimensions
|
|
346
352
|
from OEIS sequences. It serves as a rapid reference for known values without
|
|
347
353
|
requiring computation, and can be used to validate algorithm results.
|
|
348
|
-
|
|
354
|
+
|
|
349
355
|
Parameters:
|
|
350
356
|
mapShape: A tuple of integers representing the dimensions of the map.
|
|
351
|
-
|
|
357
|
+
|
|
352
358
|
Returns:
|
|
353
359
|
foldingsTotal: The known total number of foldings for the given map shape,
|
|
354
360
|
or -1 if the map shape doesn't match any known values in the OEIS sequences.
|
|
355
|
-
|
|
361
|
+
|
|
356
362
|
Notes:
|
|
357
363
|
The function uses a cached dictionary (via makeDictionaryFoldsTotalKnown) to
|
|
358
364
|
efficiently retrieve values without repeatedly parsing OEIS data. Map shape
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from mapFolding.someAssemblyRequired import ShatteredDataclass, ast_Identifier, parsePathFilename2astModule, str_nameDOTname
|
|
2
|
+
from mapFolding.someAssemblyRequired.toolboxNumba import theNumbaFlow
|
|
3
|
+
from mapFolding.someAssemblyRequired.transformationTools import shatter_dataclassesDOTdataclass
|
|
4
|
+
from mapFolding.theSSOT import ComputationState, DatatypeElephino as TheDatatypeElephino, DatatypeFoldsTotal as TheDatatypeFoldsTotal, DatatypeLeavesTotal as TheDatatypeLeavesTotal
|
|
5
|
+
from mapFolding.toolboxFilesystem import getPathFilenameFoldsTotal, getPathRootJobDEFAULT
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
from pathlib import Path, PurePosixPath
|
|
9
|
+
from typing import TypeAlias
|
|
10
|
+
|
|
11
|
+
@dataclasses.dataclass
|
|
12
|
+
class RecipeJob:
|
|
13
|
+
state: ComputationState
|
|
14
|
+
# TODO create function to calculate `foldsTotalEstimated`
|
|
15
|
+
foldsTotalEstimated: int = 0
|
|
16
|
+
shatteredDataclass: ShatteredDataclass = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
|
|
17
|
+
|
|
18
|
+
# ========================================
|
|
19
|
+
# Source
|
|
20
|
+
source_astModule = parsePathFilename2astModule(theNumbaFlow.pathFilenameSequential)
|
|
21
|
+
sourceCountCallable: ast_Identifier = theNumbaFlow.callableSequential
|
|
22
|
+
|
|
23
|
+
sourceLogicalPathModuleDataclass: str_nameDOTname = theNumbaFlow.logicalPathModuleDataclass
|
|
24
|
+
sourceDataclassIdentifier: ast_Identifier = theNumbaFlow.dataclassIdentifier
|
|
25
|
+
sourceDataclassInstance: ast_Identifier = theNumbaFlow.dataclassInstance
|
|
26
|
+
|
|
27
|
+
sourcePathPackage: PurePosixPath | None = theNumbaFlow.pathPackage
|
|
28
|
+
sourcePackageIdentifier: ast_Identifier | None = theNumbaFlow.packageIdentifier
|
|
29
|
+
|
|
30
|
+
# ========================================
|
|
31
|
+
# Filesystem (names of physical objects)
|
|
32
|
+
pathPackage: PurePosixPath | None = None
|
|
33
|
+
pathModule: PurePosixPath | None = PurePosixPath(getPathRootJobDEFAULT())
|
|
34
|
+
""" `pathModule` will override `pathPackage` and `logicalPathRoot`."""
|
|
35
|
+
fileExtension: str = theNumbaFlow.fileExtension
|
|
36
|
+
pathFilenameFoldsTotal: PurePosixPath = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
|
|
37
|
+
|
|
38
|
+
# ========================================
|
|
39
|
+
# Logical identifiers (as opposed to physical identifiers)
|
|
40
|
+
packageIdentifier: ast_Identifier | None = None
|
|
41
|
+
logicalPathRoot: str_nameDOTname | None = None
|
|
42
|
+
""" `logicalPathRoot` likely corresponds to a physical filesystem directory."""
|
|
43
|
+
moduleIdentifier: ast_Identifier = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType]
|
|
44
|
+
countCallable: ast_Identifier = sourceCountCallable
|
|
45
|
+
dataclassIdentifier: ast_Identifier | None = sourceDataclassIdentifier
|
|
46
|
+
dataclassInstance: ast_Identifier | None = sourceDataclassInstance
|
|
47
|
+
logicalPathModuleDataclass: str_nameDOTname | None = sourceLogicalPathModuleDataclass
|
|
48
|
+
|
|
49
|
+
# ========================================
|
|
50
|
+
# Datatypes
|
|
51
|
+
DatatypeFoldsTotal: TypeAlias = TheDatatypeFoldsTotal
|
|
52
|
+
DatatypeElephino: TypeAlias = TheDatatypeElephino
|
|
53
|
+
DatatypeLeavesTotal: TypeAlias = TheDatatypeLeavesTotal
|
|
54
|
+
|
|
55
|
+
def _makePathFilename(self,
|
|
56
|
+
pathRoot: PurePosixPath | None = None,
|
|
57
|
+
logicalPathINFIX: str_nameDOTname | None = None,
|
|
58
|
+
filenameStem: str | None = None,
|
|
59
|
+
fileExtension: str | None = None,
|
|
60
|
+
) -> PurePosixPath:
|
|
61
|
+
if pathRoot is None:
|
|
62
|
+
pathRoot = self.pathPackage or PurePosixPath(Path.cwd())
|
|
63
|
+
if logicalPathINFIX:
|
|
64
|
+
whyIsThisStillAThing: list[str] = logicalPathINFIX.split('.')
|
|
65
|
+
pathRoot = pathRoot.joinpath(*whyIsThisStillAThing)
|
|
66
|
+
if filenameStem is None:
|
|
67
|
+
filenameStem = self.moduleIdentifier
|
|
68
|
+
if fileExtension is None:
|
|
69
|
+
fileExtension = self.fileExtension
|
|
70
|
+
filename: str = filenameStem + fileExtension
|
|
71
|
+
return pathRoot.joinpath(filename)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def pathFilenameModule(self) -> PurePosixPath:
|
|
75
|
+
if self.pathModule is None:
|
|
76
|
+
return self._makePathFilename()
|
|
77
|
+
else:
|
|
78
|
+
return self._makePathFilename(pathRoot=self.pathModule, logicalPathINFIX=None)
|
|
79
|
+
|
|
80
|
+
def __post_init__(self):
|
|
81
|
+
pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(self.state.mapShape))
|
|
82
|
+
|
|
83
|
+
if self.moduleIdentifier is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
84
|
+
self.moduleIdentifier = pathFilenameFoldsTotal.stem
|
|
85
|
+
|
|
86
|
+
if self.pathFilenameFoldsTotal is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
87
|
+
self.pathFilenameFoldsTotal = pathFilenameFoldsTotal
|
|
88
|
+
|
|
89
|
+
if self.shatteredDataclass is None and self.logicalPathModuleDataclass and self.dataclassIdentifier and self.dataclassInstance: # pyright: ignore[reportUnnecessaryComparison]
|
|
90
|
+
self.shatteredDataclass = shatter_dataclassesDOTdataclass(self.logicalPathModuleDataclass, self.dataclassIdentifier, self.dataclassInstance)
|
|
91
|
+
|
|
92
|
+
# ========================================
|
|
93
|
+
# Fields you probably don't need =================================
|
|
94
|
+
# Dispatcher =================================
|
|
95
|
+
sourceDispatcherCallable: ast_Identifier = theNumbaFlow.callableDispatcher
|
|
96
|
+
dispatcherCallable: ast_Identifier = sourceDispatcherCallable
|
|
97
|
+
# Parallel counting =================================
|
|
98
|
+
sourceDataclassInstanceTaskDistribution: ast_Identifier = theNumbaFlow.dataclassInstanceTaskDistribution
|
|
99
|
+
sourceConcurrencyManagerNamespace: ast_Identifier = theNumbaFlow.concurrencyManagerNamespace
|
|
100
|
+
sourceConcurrencyManagerIdentifier: ast_Identifier = theNumbaFlow.concurrencyManagerIdentifier
|
|
101
|
+
dataclassInstanceTaskDistribution: ast_Identifier = sourceDataclassInstanceTaskDistribution
|
|
102
|
+
concurrencyManagerNamespace: ast_Identifier = sourceConcurrencyManagerNamespace
|
|
103
|
+
concurrencyManagerIdentifier: ast_Identifier = sourceConcurrencyManagerIdentifier
|
|
@@ -48,40 +48,40 @@ to verify correctness at each transformation stage through the integrated test s
|
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
50
|
from mapFolding.someAssemblyRequired._theTypes import (
|
|
51
|
-
ast_expr_Slice,
|
|
52
|
-
ast_Identifier,
|
|
53
|
-
astClassHasDOTnameNotName,
|
|
54
|
-
astClassHasDOTtarget,
|
|
55
|
-
astClassHasDOTvalue_expr,
|
|
56
|
-
astClassHasDOTvalue_exprNone,
|
|
57
|
-
astClassHasDOTtargetAttributeNameSubscript,
|
|
58
|
-
astClassHasDOTtarget_expr,
|
|
59
|
-
astClassHasDOTvalue,
|
|
60
|
-
astClassOptionallyHasDOTnameNotName,
|
|
61
|
-
intORlist_ast_type_paramORstr_orNone,
|
|
62
|
-
intORstr_orNone,
|
|
63
|
-
list_ast_type_paramORstr_orNone,
|
|
64
|
-
str_nameDOTname,
|
|
65
|
-
个,
|
|
51
|
+
ast_expr_Slice as ast_expr_Slice,
|
|
52
|
+
ast_Identifier as ast_Identifier,
|
|
53
|
+
astClassHasDOTnameNotName as astClassHasDOTnameNotName,
|
|
54
|
+
astClassHasDOTtarget as astClassHasDOTtarget,
|
|
55
|
+
astClassHasDOTvalue_expr as astClassHasDOTvalue_expr,
|
|
56
|
+
astClassHasDOTvalue_exprNone as astClassHasDOTvalue_exprNone,
|
|
57
|
+
astClassHasDOTtargetAttributeNameSubscript as astClassHasDOTtargetAttributeNameSubscript,
|
|
58
|
+
astClassHasDOTtarget_expr as astClassHasDOTtarget_expr,
|
|
59
|
+
astClassHasDOTvalue as astClassHasDOTvalue,
|
|
60
|
+
astClassOptionallyHasDOTnameNotName as astClassOptionallyHasDOTnameNotName,
|
|
61
|
+
intORlist_ast_type_paramORstr_orNone as intORlist_ast_type_paramORstr_orNone,
|
|
62
|
+
intORstr_orNone as intORstr_orNone,
|
|
63
|
+
list_ast_type_paramORstr_orNone as list_ast_type_paramORstr_orNone,
|
|
64
|
+
str_nameDOTname as str_nameDOTname,
|
|
65
|
+
个 as 个,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
from mapFolding.someAssemblyRequired._toolboxPython import (
|
|
69
|
-
importLogicalPath2Callable,
|
|
70
|
-
importPathFilename2Callable,
|
|
71
|
-
NodeChanger,
|
|
72
|
-
NodeTourist,
|
|
73
|
-
parseLogicalPath2astModule,
|
|
74
|
-
parsePathFilename2astModule,
|
|
69
|
+
importLogicalPath2Callable as importLogicalPath2Callable,
|
|
70
|
+
importPathFilename2Callable as importPathFilename2Callable,
|
|
71
|
+
NodeChanger as NodeChanger,
|
|
72
|
+
NodeTourist as NodeTourist,
|
|
73
|
+
parseLogicalPath2astModule as parseLogicalPath2astModule,
|
|
74
|
+
parsePathFilename2astModule as parsePathFilename2astModule,
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
-
from mapFolding.someAssemblyRequired._toolboxAntecedents import DOT, ifThis
|
|
78
|
-
from mapFolding.someAssemblyRequired._tool_Make import Make
|
|
79
|
-
from mapFolding.someAssemblyRequired._tool_Then import grab, Then
|
|
77
|
+
from mapFolding.someAssemblyRequired._toolboxAntecedents import be as be, DOT as DOT, ifThis as ifThis
|
|
78
|
+
from mapFolding.someAssemblyRequired._tool_Make import Make as Make
|
|
79
|
+
from mapFolding.someAssemblyRequired._tool_Then import grab as grab, Then as Then
|
|
80
80
|
|
|
81
81
|
from mapFolding.someAssemblyRequired._toolboxContainers import (
|
|
82
|
-
IngredientsFunction,
|
|
83
|
-
IngredientsModule,
|
|
84
|
-
LedgerOfImports,
|
|
85
|
-
RecipeSynthesizeFlow,
|
|
86
|
-
ShatteredDataclass,
|
|
82
|
+
IngredientsFunction as IngredientsFunction,
|
|
83
|
+
IngredientsModule as IngredientsModule,
|
|
84
|
+
LedgerOfImports as LedgerOfImports,
|
|
85
|
+
RecipeSynthesizeFlow as RecipeSynthesizeFlow,
|
|
86
|
+
ShatteredDataclass as ShatteredDataclass,
|
|
87
87
|
)
|
|
@@ -56,7 +56,7 @@ class Make:
|
|
|
56
56
|
return ast.arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
|
|
57
57
|
|
|
58
58
|
@staticmethod
|
|
59
|
-
def Assign(listTargets:
|
|
59
|
+
def Assign(listTargets: list[ast.expr], value: ast.expr, **keywordArguments: intORstr_orNone) -> ast.Assign:
|
|
60
60
|
return ast.Assign(listTargets, value, **keywordArguments)
|
|
61
61
|
|
|
62
62
|
@staticmethod
|
{mapfolding-0.9.0 → mapfolding-0.9.1}/mapFolding/someAssemblyRequired/_toolboxAntecedents.py
RENAMED
|
@@ -231,3 +231,15 @@ class ifThis:
|
|
|
231
231
|
def Z0Z_unparseIs(astAST: ast.AST) -> Callable[[ast.AST], bool]:
|
|
232
232
|
def workhorse(node: ast.AST) -> bool: return ast.unparse(node) == ast.unparse(astAST)
|
|
233
233
|
return workhorse
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class be:
|
|
237
|
+
@staticmethod
|
|
238
|
+
def Call(node: ast.AST) -> TypeGuard[ast.Call]:
|
|
239
|
+
return isinstance(node, ast.Call)
|
|
240
|
+
@staticmethod
|
|
241
|
+
def Name(node: ast.AST) -> TypeGuard[ast.Name]:
|
|
242
|
+
return isinstance(node, ast.Name)
|
|
243
|
+
@staticmethod
|
|
244
|
+
def Return(node: ast.AST) -> TypeGuard[ast.Return]:
|
|
245
|
+
return isinstance(node, ast.Return)
|
|
@@ -29,7 +29,6 @@ from Z0Z_tools import updateExtendPolishDictionaryLists
|
|
|
29
29
|
import ast
|
|
30
30
|
import dataclasses
|
|
31
31
|
|
|
32
|
-
# Consolidate settings classes through inheritance https://github.com/hunterhogan/mapFolding/issues/15
|
|
33
32
|
class LedgerOfImports:
|
|
34
33
|
"""
|
|
35
34
|
Track and manage import statements for programmatically generated code.
|
|
@@ -58,23 +57,22 @@ class LedgerOfImports:
|
|
|
58
57
|
self.walkThis(startWith)
|
|
59
58
|
|
|
60
59
|
def addAst(self, astImport____: ast.Import | ast.ImportFrom) -> None:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
60
|
+
match astImport____:
|
|
61
|
+
case ast.Import():
|
|
62
|
+
for alias in astImport____.names:
|
|
63
|
+
self.listImport.append(alias.name)
|
|
64
|
+
case ast.ImportFrom():
|
|
65
|
+
# TODO fix the mess created by `None` means '.'. I need a `str_nameDOTname` to replace '.'
|
|
66
|
+
if astImport____.module is None:
|
|
67
|
+
astImport____.module = '.'
|
|
68
|
+
for alias in astImport____.names:
|
|
69
|
+
self.dictionaryImportFrom[astImport____.module].append((alias.name, alias.asname))
|
|
70
|
+
case _:
|
|
71
|
+
raise ValueError(f"I received {type(astImport____) = }, but I can only accept {ast.Import} and {ast.ImportFrom}.")
|
|
71
72
|
|
|
72
73
|
def addImport_asStr(self, moduleWithLogicalPath: str_nameDOTname) -> None:
|
|
73
74
|
self.listImport.append(moduleWithLogicalPath)
|
|
74
75
|
|
|
75
|
-
# def addImportFrom_asStr(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier, asname: ast_Identifier | None = None) -> None:
|
|
76
|
-
# self.dictionaryImportFrom[moduleWithLogicalPath].append((name, asname))
|
|
77
|
-
|
|
78
76
|
def addImportFrom_asStr(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier, asname: ast_Identifier | None = None) -> None:
|
|
79
77
|
if moduleWithLogicalPath not in self.dictionaryImportFrom:
|
|
80
78
|
self.dictionaryImportFrom[moduleWithLogicalPath] = []
|
|
@@ -85,15 +83,14 @@ class LedgerOfImports:
|
|
|
85
83
|
self.removeImportFrom(moduleWithLogicalPath, None, None)
|
|
86
84
|
|
|
87
85
|
def removeImportFrom(self, moduleWithLogicalPath: str_nameDOTname, name: ast_Identifier | None, asname: ast_Identifier | None = None) -> None:
|
|
88
|
-
|
|
86
|
+
"""
|
|
87
|
+
name, asname Action
|
|
88
|
+
None, None : remove all matches for the module
|
|
89
|
+
ast_Identifier, ast_Identifier : remove exact matches
|
|
90
|
+
ast_Identifier, None : remove exact matches
|
|
91
|
+
None, ast_Identifier : remove all matches for asname and if entry_asname is None remove name == ast_Identifier
|
|
92
|
+
"""
|
|
89
93
|
if moduleWithLogicalPath in self.dictionaryImportFrom:
|
|
90
|
-
"""
|
|
91
|
-
name, asname Meaning
|
|
92
|
-
ast_Identifier, ast_Identifier : remove exact matches
|
|
93
|
-
ast_Identifier, None : remove exact matches
|
|
94
|
-
None, ast_Identifier : remove all matches for asname and if entry_asname is None remove name == ast_Identifier
|
|
95
|
-
None, None : remove all matches for the module
|
|
96
|
-
"""
|
|
97
94
|
if name is None and asname is None:
|
|
98
95
|
# Remove all entries for the module
|
|
99
96
|
self.dictionaryImportFrom.pop(moduleWithLogicalPath)
|
|
@@ -102,7 +99,6 @@ class LedgerOfImports:
|
|
|
102
99
|
self.dictionaryImportFrom[moduleWithLogicalPath] = [(entry_name, entry_asname) for entry_name, entry_asname in self.dictionaryImportFrom[moduleWithLogicalPath]
|
|
103
100
|
if not (entry_asname == asname) and not (entry_asname is None and entry_name == asname)]
|
|
104
101
|
else:
|
|
105
|
-
# Remove exact matches for the module
|
|
106
102
|
self.dictionaryImportFrom[moduleWithLogicalPath] = [(entry_name, entry_asname) for entry_name, entry_asname in self.dictionaryImportFrom[moduleWithLogicalPath]
|
|
107
103
|
if not (entry_name == name and entry_asname == asname)]
|
|
108
104
|
if not self.dictionaryImportFrom[moduleWithLogicalPath]:
|
|
@@ -308,7 +304,7 @@ class IngredientsModule:
|
|
|
308
304
|
@dataclasses.dataclass
|
|
309
305
|
class RecipeSynthesizeFlow:
|
|
310
306
|
"""
|
|
311
|
-
Configure the generation of
|
|
307
|
+
Configure the generation of new modules, including Numba-accelerated code modules.
|
|
312
308
|
|
|
313
309
|
RecipeSynthesizeFlow defines the complete blueprint for transforming an original
|
|
314
310
|
Python algorithm into an optimized, accelerated implementation. It specifies:
|
|
@@ -358,7 +354,7 @@ class RecipeSynthesizeFlow:
|
|
|
358
354
|
""" `logicalPathFlowRoot` likely corresponds to a physical filesystem directory."""
|
|
359
355
|
|
|
360
356
|
# Module ================================
|
|
361
|
-
moduleDispatcher: ast_Identifier = '
|
|
357
|
+
moduleDispatcher: ast_Identifier = 'numbaCount'
|
|
362
358
|
moduleInitialize: ast_Identifier = moduleDispatcher
|
|
363
359
|
moduleParallel: ast_Identifier = moduleDispatcher
|
|
364
360
|
moduleSequential: ast_Identifier = moduleDispatcher
|
|
@@ -376,6 +372,10 @@ class RecipeSynthesizeFlow:
|
|
|
376
372
|
dataclassInstance: ast_Identifier = sourceDataclassInstance
|
|
377
373
|
dataclassInstanceTaskDistribution: ast_Identifier = sourceDataclassInstanceTaskDistribution
|
|
378
374
|
|
|
375
|
+
removeDataclassDispatcher: bool = False
|
|
376
|
+
removeDataclassInitialize: bool = False
|
|
377
|
+
removeDataclassParallel: bool = True
|
|
378
|
+
removeDataclassSequential: bool = True
|
|
379
379
|
# ========================================
|
|
380
380
|
# Computed
|
|
381
381
|
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
@@ -432,10 +432,10 @@ class ShatteredDataclass:
|
|
|
432
432
|
countingVariableName: ast.Name
|
|
433
433
|
"""AST name node representing the counting variable identifier."""
|
|
434
434
|
|
|
435
|
-
field2AnnAssign: dict[ast_Identifier, ast.AnnAssign] = dataclasses.field(default_factory=dict)
|
|
435
|
+
field2AnnAssign: dict[ast_Identifier, ast.AnnAssign | ast.Assign] = dataclasses.field(default_factory=dict)
|
|
436
436
|
"""Maps field names to their corresponding AST call expressions."""
|
|
437
437
|
|
|
438
|
-
Z0Z_field2AnnAssign: dict[ast_Identifier, tuple[ast.AnnAssign, str]] = dataclasses.field(default_factory=dict)
|
|
438
|
+
Z0Z_field2AnnAssign: dict[ast_Identifier, tuple[ast.AnnAssign | ast.Assign, str]] = dataclasses.field(default_factory=dict)
|
|
439
439
|
|
|
440
440
|
fragments4AssignmentOrParameters: ast.Tuple = dummyTuple
|
|
441
441
|
"""AST tuple used as target for assignment to capture returned fragments."""
|
|
@@ -107,6 +107,7 @@ def importPathFilename2Callable(pathFilename: PathLike[Any] | PurePath, identifi
|
|
|
107
107
|
Load a callable (function, class, etc.) from a Python file.
|
|
108
108
|
This function imports a specified Python file as a module, extracts a callable object
|
|
109
109
|
from it by name, and returns that callable.
|
|
110
|
+
|
|
110
111
|
Parameters
|
|
111
112
|
----------
|
|
112
113
|
pathFilename : Union[PathLike[Any], PurePath]
|
|
@@ -115,10 +116,12 @@ def importPathFilename2Callable(pathFilename: PathLike[Any] | PurePath, identifi
|
|
|
115
116
|
Name of the callable to extract from the imported module.
|
|
116
117
|
moduleIdentifier : Optional[str]
|
|
117
118
|
Name to use for the imported module. If None, the filename stem is used.
|
|
119
|
+
|
|
118
120
|
Returns
|
|
119
121
|
-------
|
|
120
122
|
Callable[..., Any]
|
|
121
123
|
The callable object extracted from the imported module.
|
|
124
|
+
|
|
122
125
|
Raises
|
|
123
126
|
------
|
|
124
127
|
ImportError
|
|
@@ -19,12 +19,22 @@ as Python scripts or further compiled into standalone executables.
|
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
from mapFolding.toolboxFilesystem import getPathFilenameFoldsTotal
|
|
22
|
-
from mapFolding.someAssemblyRequired import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
from mapFolding.someAssemblyRequired import (
|
|
23
|
+
ast_Identifier,
|
|
24
|
+
be,
|
|
25
|
+
ifThis,
|
|
26
|
+
IngredientsFunction,
|
|
27
|
+
IngredientsModule,
|
|
28
|
+
LedgerOfImports,
|
|
29
|
+
Make,
|
|
30
|
+
NodeChanger,
|
|
31
|
+
NodeTourist,
|
|
32
|
+
Then,
|
|
33
|
+
)
|
|
34
|
+
from mapFolding.someAssemblyRequired.toolboxNumba import parametersNumbaLight, SpicesJobNumba, decorateCallableWithNumba
|
|
35
|
+
from mapFolding.someAssemblyRequired.transformationTools import extractFunctionDef, write_astModule, makeInitializedComputationState
|
|
36
|
+
from mapFolding.someAssemblyRequired.RecipeJob import RecipeJob
|
|
37
|
+
from mapFolding import The, raiseIfNoneGitHubIssueNumber3, getFoldsTotalKnown
|
|
28
38
|
from typing import cast
|
|
29
39
|
from Z0Z_tools import autoDecodingRLE
|
|
30
40
|
from pathlib import PurePosixPath
|
|
@@ -89,6 +99,10 @@ if __name__ == '__main__':
|
|
|
89
99
|
countWithProgressBar = NodeChanger(findThis, doThat)
|
|
90
100
|
countWithProgressBar.visit(ingredientsFunction.astFunctionDef)
|
|
91
101
|
|
|
102
|
+
removeReturnStatement = NodeChanger(be.Return, Then.removeIt)
|
|
103
|
+
removeReturnStatement.visit(ingredientsFunction.astFunctionDef)
|
|
104
|
+
ingredientsFunction.astFunctionDef.returns = Make.Constant(value=None)
|
|
105
|
+
|
|
92
106
|
ingredientsModule.appendLauncher(ast.parse(linesLaunch))
|
|
93
107
|
|
|
94
108
|
return ingredientsModule, ingredientsFunction
|
|
@@ -119,9 +133,13 @@ def move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsFunction: Ingre
|
|
|
119
133
|
"""
|
|
120
134
|
ingredientsFunction.imports.update(job.shatteredDataclass.ledger)
|
|
121
135
|
|
|
122
|
-
list_IdentifiersNotUsed = list_IdentifiersNotUsedHARDCODED
|
|
123
|
-
|
|
124
136
|
list_argCuzMyBrainRefusesToThink = ingredientsFunction.astFunctionDef.args.args + ingredientsFunction.astFunctionDef.args.posonlyargs + ingredientsFunction.astFunctionDef.args.kwonlyargs
|
|
137
|
+
list_arg_arg: list[ast_Identifier] = [ast_arg.arg for ast_arg in list_argCuzMyBrainRefusesToThink]
|
|
138
|
+
listName: list[ast.Name] = []
|
|
139
|
+
NodeTourist(be.Name, Then.appendTo(listName)).visit(ingredientsFunction.astFunctionDef)
|
|
140
|
+
list_Identifiers: list[ast_Identifier] = [astName.id for astName in listName]
|
|
141
|
+
list_IdentifiersNotUsed: list[ast_Identifier] = list(set(list_arg_arg) - set(list_Identifiers))
|
|
142
|
+
|
|
125
143
|
for ast_arg in list_argCuzMyBrainRefusesToThink:
|
|
126
144
|
if ast_arg.arg in job.shatteredDataclass.field2AnnAssign:
|
|
127
145
|
if ast_arg.arg in list_IdentifiersNotUsed:
|
|
@@ -181,11 +199,6 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
181
199
|
if not astFunctionDef: raise raiseIfNoneGitHubIssueNumber3
|
|
182
200
|
ingredientsCount: IngredientsFunction = IngredientsFunction(astFunctionDef, LedgerOfImports())
|
|
183
201
|
|
|
184
|
-
# Change the return so you can dynamically determine which variables are not used
|
|
185
|
-
removeReturnStatement = NodeChanger(lambda node: isinstance(node, ast.Return), Then.removeIt) # type: ignore
|
|
186
|
-
removeReturnStatement.visit(ingredientsCount.astFunctionDef)
|
|
187
|
-
ingredientsCount.astFunctionDef.returns = Make.Constant(value=None)
|
|
188
|
-
|
|
189
202
|
# Remove `foldGroups` and any other unused statements, so you can dynamically determine which variables are not used
|
|
190
203
|
findThis = ifThis.isAssignAndTargets0Is(ifThis.isSubscript_Identifier('foldGroups'))
|
|
191
204
|
doThat = Then.removeIt
|
|
@@ -199,30 +212,48 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
199
212
|
doThat = Then.replaceWith(Make.Constant(int(job.state.__dict__[identifier])))
|
|
200
213
|
NodeChanger(findThis, doThat).visit(ingredientsCount.astFunctionDef)
|
|
201
214
|
|
|
202
|
-
# This launcher eliminates the use of one identifier, so run it now and you can dynamically determine which variables are not used
|
|
203
215
|
ingredientsModule = IngredientsModule()
|
|
216
|
+
# This launcher eliminates the use of one identifier, so run it now and you can dynamically determine which variables are not used
|
|
204
217
|
if spices.useNumbaProgressBar:
|
|
205
218
|
ingredientsModule, ingredientsCount = addLauncherNumbaProgress(ingredientsModule, ingredientsCount, job, spices)
|
|
206
219
|
spices.parametersNumba['nogil'] = True
|
|
220
|
+
else:
|
|
221
|
+
linesLaunch: str = f"""
|
|
222
|
+
if __name__ == '__main__':
|
|
223
|
+
import time
|
|
224
|
+
timeStart = time.perf_counter()
|
|
225
|
+
foldsTotal = {job.countCallable}() * {job.state.leavesTotal}
|
|
226
|
+
print(time.perf_counter() - timeStart)
|
|
227
|
+
print('\\nmap {job.state.mapShape} =', foldsTotal)
|
|
228
|
+
writeStream = open('{job.pathFilenameFoldsTotal.as_posix()}', 'w')
|
|
229
|
+
writeStream.write(str(foldsTotal))
|
|
230
|
+
writeStream.close()
|
|
231
|
+
from mapFolding.oeis import getFoldsTotalKnown
|
|
232
|
+
print(foldsTotal == getFoldsTotalKnown({job.state.mapShape}))
|
|
233
|
+
"""
|
|
234
|
+
ingredientsModule.appendLauncher(ast.parse(linesLaunch))
|
|
235
|
+
changeReturnParallelCallable = NodeChanger(be.Return, Then.replaceWith(Make.Return(job.shatteredDataclass.countingVariableName)))
|
|
236
|
+
changeReturnParallelCallable.visit(ingredientsCount.astFunctionDef)
|
|
237
|
+
ingredientsCount.astFunctionDef.returns = job.shatteredDataclass.countingVariableAnnotation
|
|
207
238
|
|
|
208
239
|
ingredientsCount = move_arg2FunctionDefDOTbodyAndAssignInitialValues(ingredientsCount, job)
|
|
209
240
|
|
|
210
241
|
Z0Z_Identifier = 'DatatypeLeavesTotal'
|
|
211
242
|
Z0Z_type = 'uint8'
|
|
212
243
|
ingredientsModule.imports.addImportFrom_asStr('numba', Z0Z_type)
|
|
213
|
-
|
|
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))
|
|
244
|
+
Z0Z_statement = Make.Assign([Make.Name(Z0Z_Identifier, ast.Store())], Make.Name(Z0Z_type))
|
|
217
245
|
ingredientsModule.appendPrologue(statement=Z0Z_statement)
|
|
218
246
|
|
|
219
247
|
Z0Z_Identifier = 'DatatypeElephino'
|
|
220
|
-
Z0Z_type = '
|
|
248
|
+
Z0Z_type = 'uint8'
|
|
249
|
+
ingredientsModule.imports.addImportFrom_asStr('numba', Z0Z_type)
|
|
250
|
+
Z0Z_statement = Make.Assign([Make.Name(Z0Z_Identifier, ast.Store())], Make.Name(Z0Z_type))
|
|
251
|
+
ingredientsModule.appendPrologue(statement=Z0Z_statement)
|
|
252
|
+
|
|
253
|
+
Z0Z_Identifier = 'DatatypeFoldsTotal'
|
|
254
|
+
Z0Z_type = 'int64'
|
|
221
255
|
ingredientsModule.imports.addImportFrom_asStr('numba', Z0Z_type)
|
|
222
|
-
|
|
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))
|
|
256
|
+
Z0Z_statement = Make.Assign([Make.Name(Z0Z_Identifier, ast.Store())], Make.Name(Z0Z_type))
|
|
226
257
|
ingredientsModule.appendPrologue(statement=Z0Z_statement)
|
|
227
258
|
|
|
228
259
|
ingredientsCount.imports.removeImportFromModule('mapFolding.theSSOT')
|
|
@@ -233,7 +264,7 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
233
264
|
ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
|
|
234
265
|
Z0Z_asname = 'Array1DElephino'
|
|
235
266
|
ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
|
|
236
|
-
Z0Z_type_name = '
|
|
267
|
+
Z0Z_type_name = 'uint8'
|
|
237
268
|
ingredientsCount.imports.addImportFrom_asStr(Z0Z_module, Z0Z_type_name, Z0Z_asname)
|
|
238
269
|
Z0Z_asname = 'Array3D'
|
|
239
270
|
ingredientsCount.imports.removeImportFrom(Z0Z_module, None, Z0Z_asname)
|
|
@@ -269,11 +300,12 @@ def makeJobNumba(job: RecipeJob, spices: SpicesJobNumba) -> None:
|
|
|
269
300
|
"""
|
|
270
301
|
|
|
271
302
|
if __name__ == '__main__':
|
|
272
|
-
mapShape = (
|
|
303
|
+
mapShape = (1,24)
|
|
273
304
|
state = makeInitializedComputationState(mapShape)
|
|
274
305
|
foldsTotalEstimated = getFoldsTotalKnown(state.mapShape) // state.leavesTotal
|
|
275
306
|
pathModule = PurePosixPath(The.pathPackage, 'jobs')
|
|
276
307
|
pathFilenameFoldsTotal = PurePosixPath(getPathFilenameFoldsTotal(state.mapShape, pathModule))
|
|
277
308
|
aJob = RecipeJob(state, foldsTotalEstimated, pathModule=pathModule, pathFilenameFoldsTotal=pathFilenameFoldsTotal)
|
|
278
|
-
spices = SpicesJobNumba()
|
|
309
|
+
spices = SpicesJobNumba(useNumbaProgressBar=False, parametersNumba=parametersNumbaLight)
|
|
310
|
+
# spices = SpicesJobNumba()
|
|
279
311
|
makeJobNumba(aJob, spices)
|