mapFolding 0.11.1__py3-none-any.whl → 0.11.3__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 (35) hide show
  1. mapFolding/__init__.py +7 -60
  2. mapFolding/basecamp.py +15 -13
  3. mapFolding/beDRY.py +4 -36
  4. mapFolding/dataBaskets.py +24 -2
  5. mapFolding/datatypes.py +0 -3
  6. mapFolding/{toolboxFilesystem.py → filesystemToolkit.py} +3 -3
  7. mapFolding/oeis.py +3 -5
  8. mapFolding/someAssemblyRequired/RecipeJob.py +8 -116
  9. mapFolding/someAssemblyRequired/Z0Z_makeAllModules.py +492 -0
  10. mapFolding/someAssemblyRequired/__init__.py +5 -31
  11. mapFolding/someAssemblyRequired/_toolIfThis.py +5 -6
  12. mapFolding/someAssemblyRequired/{_toolboxContainers.py → _toolkitContainers.py} +6 -127
  13. mapFolding/someAssemblyRequired/infoBooth.py +70 -0
  14. mapFolding/someAssemblyRequired/makeJobTheorem2Numba.py +13 -12
  15. mapFolding/someAssemblyRequired/{toolboxNumba.py → toolkitNumba.py} +2 -44
  16. mapFolding/someAssemblyRequired/transformationTools.py +16 -174
  17. mapFolding/syntheticModules/countParallel.py +98 -0
  18. mapFolding/syntheticModules/dataPacking.py +1 -1
  19. mapFolding/theSSOT.py +12 -246
  20. {mapfolding-0.11.1.dist-info → mapfolding-0.11.3.dist-info}/METADATA +16 -11
  21. mapfolding-0.11.3.dist-info/RECORD +53 -0
  22. {mapfolding-0.11.1.dist-info → mapfolding-0.11.3.dist-info}/WHEEL +1 -1
  23. tests/conftest.py +2 -79
  24. tests/test_computations.py +12 -19
  25. tests/test_filesystem.py +1 -2
  26. tests/test_other.py +1 -1
  27. tests/test_tasks.py +3 -4
  28. mapFolding/someAssemblyRequired/Z0Z_makeSomeModules.py +0 -325
  29. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +0 -314
  30. mapFolding/syntheticModules/numbaCount.py +0 -201
  31. mapFolding/theDao.py +0 -243
  32. mapfolding-0.11.1.dist-info/RECORD +0 -54
  33. {mapfolding-0.11.1.dist-info → mapfolding-0.11.3.dist-info}/entry_points.txt +0 -0
  34. {mapfolding-0.11.1.dist-info → mapfolding-0.11.3.dist-info}/licenses/LICENSE +0 -0
  35. {mapfolding-0.11.1.dist-info → mapfolding-0.11.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,98 @@
1
+ from concurrent.futures import Future as ConcurrentFuture, ProcessPoolExecutor
2
+ from copy import deepcopy
3
+ from mapFolding.dataBaskets import Array1DElephino, Array1DLeavesTotal, Array3D, DatatypeElephino, DatatypeFoldsTotal, DatatypeLeavesTotal, ParallelMapFoldingState
4
+ from multiprocessing import set_start_method as multiprocessing_set_start_method
5
+ from numba import jit
6
+ if __name__ == '__main__':
7
+ multiprocessing_set_start_method('spawn')
8
+
9
+ @jit(cache=True, error_model='numpy', fastmath=True, forceinline=True)
10
+ def count(groupsOfFolds: DatatypeFoldsTotal, gap1ndex: DatatypeElephino, gap1ndexCeiling: DatatypeElephino, indexDimension: DatatypeLeavesTotal, indexLeaf: DatatypeLeavesTotal, indexMiniGap: DatatypeElephino, leaf1ndex: DatatypeLeavesTotal, leafConnectee: DatatypeLeavesTotal, dimensionsUnconstrained: DatatypeLeavesTotal, countDimensionsGapped: Array1DLeavesTotal, gapRangeStart: Array1DElephino, gapsWhere: Array1DLeavesTotal, leafAbove: Array1DLeavesTotal, leafBelow: Array1DLeavesTotal, connectionGraph: Array3D, dimensionsTotal: DatatypeLeavesTotal, leavesTotal: DatatypeLeavesTotal, taskDivisions: DatatypeLeavesTotal, taskIndex: DatatypeLeavesTotal) -> tuple[DatatypeFoldsTotal, DatatypeElephino, DatatypeElephino, DatatypeLeavesTotal, DatatypeLeavesTotal, DatatypeElephino, DatatypeLeavesTotal, DatatypeLeavesTotal, DatatypeLeavesTotal, Array1DLeavesTotal, Array1DElephino, Array1DLeavesTotal, Array1DLeavesTotal, Array1DLeavesTotal, Array3D, DatatypeLeavesTotal, DatatypeLeavesTotal, DatatypeLeavesTotal, DatatypeLeavesTotal]:
11
+ while leaf1ndex > 0:
12
+ if leaf1ndex <= 1 or leafBelow[0] == 1:
13
+ if leaf1ndex > leavesTotal:
14
+ groupsOfFolds += 1
15
+ else:
16
+ dimensionsUnconstrained = dimensionsTotal
17
+ gap1ndexCeiling = gapRangeStart[leaf1ndex - 1]
18
+ indexDimension = 0
19
+ while indexDimension < dimensionsTotal:
20
+ leafConnectee = connectionGraph[indexDimension, leaf1ndex, leaf1ndex]
21
+ if leafConnectee == leaf1ndex:
22
+ dimensionsUnconstrained -= 1
23
+ else:
24
+ while leafConnectee != leaf1ndex:
25
+ if leaf1ndex != taskDivisions or leafConnectee % taskDivisions == taskIndex:
26
+ gapsWhere[gap1ndexCeiling] = leafConnectee
27
+ if countDimensionsGapped[leafConnectee] == 0:
28
+ gap1ndexCeiling += 1
29
+ countDimensionsGapped[leafConnectee] += 1
30
+ leafConnectee = connectionGraph[indexDimension, leaf1ndex, leafBelow[leafConnectee]]
31
+ indexDimension += 1
32
+ if not dimensionsUnconstrained:
33
+ indexLeaf = 0
34
+ while indexLeaf < leaf1ndex:
35
+ gapsWhere[gap1ndexCeiling] = indexLeaf
36
+ gap1ndexCeiling += 1
37
+ indexLeaf += 1
38
+ indexMiniGap = gap1ndex
39
+ while indexMiniGap < gap1ndexCeiling:
40
+ gapsWhere[gap1ndex] = gapsWhere[indexMiniGap]
41
+ if countDimensionsGapped[gapsWhere[indexMiniGap]] == dimensionsUnconstrained:
42
+ gap1ndex += 1
43
+ countDimensionsGapped[gapsWhere[indexMiniGap]] = 0
44
+ indexMiniGap += 1
45
+ while leaf1ndex > 0 and gap1ndex == gapRangeStart[leaf1ndex - 1]:
46
+ leaf1ndex -= 1
47
+ leafBelow[leafAbove[leaf1ndex]] = leafBelow[leaf1ndex]
48
+ leafAbove[leafBelow[leaf1ndex]] = leafAbove[leaf1ndex]
49
+ if leaf1ndex > 0:
50
+ gap1ndex -= 1
51
+ leafAbove[leaf1ndex] = gapsWhere[gap1ndex]
52
+ leafBelow[leaf1ndex] = leafBelow[leafAbove[leaf1ndex]]
53
+ leafBelow[leafAbove[leaf1ndex]] = leaf1ndex
54
+ leafAbove[leafBelow[leaf1ndex]] = leaf1ndex
55
+ gapRangeStart[leaf1ndex] = gap1ndex
56
+ leaf1ndex += 1
57
+ return (groupsOfFolds, gap1ndex, gap1ndexCeiling, indexDimension, indexLeaf, indexMiniGap, leaf1ndex, leafConnectee, dimensionsUnconstrained, countDimensionsGapped, gapRangeStart, gapsWhere, leafAbove, leafBelow, connectionGraph, dimensionsTotal, leavesTotal, taskDivisions, taskIndex)
58
+
59
+ def unRepackParallelMapFoldingState(state: ParallelMapFoldingState) -> ParallelMapFoldingState:
60
+ mapShape: tuple[DatatypeLeavesTotal, ...] = state.mapShape
61
+ groupsOfFolds: DatatypeFoldsTotal = state.groupsOfFolds
62
+ gap1ndex: DatatypeElephino = state.gap1ndex
63
+ gap1ndexCeiling: DatatypeElephino = state.gap1ndexCeiling
64
+ indexDimension: DatatypeLeavesTotal = state.indexDimension
65
+ indexLeaf: DatatypeLeavesTotal = state.indexLeaf
66
+ indexMiniGap: DatatypeElephino = state.indexMiniGap
67
+ leaf1ndex: DatatypeLeavesTotal = state.leaf1ndex
68
+ leafConnectee: DatatypeLeavesTotal = state.leafConnectee
69
+ dimensionsUnconstrained: DatatypeLeavesTotal = state.dimensionsUnconstrained
70
+ countDimensionsGapped: Array1DLeavesTotal = state.countDimensionsGapped
71
+ gapRangeStart: Array1DElephino = state.gapRangeStart
72
+ gapsWhere: Array1DLeavesTotal = state.gapsWhere
73
+ leafAbove: Array1DLeavesTotal = state.leafAbove
74
+ leafBelow: Array1DLeavesTotal = state.leafBelow
75
+ connectionGraph: Array3D = state.connectionGraph
76
+ dimensionsTotal: DatatypeLeavesTotal = state.dimensionsTotal
77
+ leavesTotal: DatatypeLeavesTotal = state.leavesTotal
78
+ taskDivisions: DatatypeLeavesTotal = state.taskDivisions
79
+ taskIndex: DatatypeLeavesTotal = state.taskIndex
80
+ groupsOfFolds, gap1ndex, gap1ndexCeiling, indexDimension, indexLeaf, indexMiniGap, leaf1ndex, leafConnectee, dimensionsUnconstrained, countDimensionsGapped, gapRangeStart, gapsWhere, leafAbove, leafBelow, connectionGraph, dimensionsTotal, leavesTotal, taskDivisions, taskIndex = count(groupsOfFolds, gap1ndex, gap1ndexCeiling, indexDimension, indexLeaf, indexMiniGap, leaf1ndex, leafConnectee, dimensionsUnconstrained, countDimensionsGapped, gapRangeStart, gapsWhere, leafAbove, leafBelow, connectionGraph, dimensionsTotal, leavesTotal, taskDivisions, taskIndex)
81
+ state = ParallelMapFoldingState(mapShape=mapShape, groupsOfFolds=groupsOfFolds, gap1ndex=gap1ndex, gap1ndexCeiling=gap1ndexCeiling, indexDimension=indexDimension, indexLeaf=indexLeaf, indexMiniGap=indexMiniGap, leaf1ndex=leaf1ndex, leafConnectee=leafConnectee, dimensionsUnconstrained=dimensionsUnconstrained, countDimensionsGapped=countDimensionsGapped, gapRangeStart=gapRangeStart, gapsWhere=gapsWhere, leafAbove=leafAbove, leafBelow=leafBelow, taskDivisions=taskDivisions, taskIndex=taskIndex)
82
+ return state
83
+
84
+ def doTheNeedful(state: ParallelMapFoldingState, concurrencyLimit: int) -> tuple[int, list[ParallelMapFoldingState]]:
85
+ stateParallel = deepcopy(state)
86
+ listStatesParallel: list[ParallelMapFoldingState] = [stateParallel] * stateParallel.taskDivisions
87
+ groupsOfFoldsTotal: int = 0
88
+ dictionaryConcurrency: dict[int, ConcurrentFuture[ParallelMapFoldingState]] = {}
89
+ with ProcessPoolExecutor(concurrencyLimit) as concurrencyManager:
90
+ for indexSherpa in range(stateParallel.taskDivisions):
91
+ state = deepcopy(stateParallel)
92
+ state.taskIndex = indexSherpa
93
+ dictionaryConcurrency[indexSherpa] = concurrencyManager.submit(unRepackParallelMapFoldingState, state)
94
+ for indexSherpa in range(stateParallel.taskDivisions):
95
+ listStatesParallel[indexSherpa] = dictionaryConcurrency[indexSherpa].result()
96
+ groupsOfFoldsTotal += listStatesParallel[indexSherpa].groupsOfFolds
97
+ foldsTotal: int = groupsOfFoldsTotal * stateParallel.leavesTotal
98
+ return (foldsTotal, listStatesParallel)
@@ -1,7 +1,7 @@
1
1
  from mapFolding.dataBaskets import Array1DElephino, Array1DLeavesTotal, Array3D, DatatypeElephino, DatatypeFoldsTotal, DatatypeLeavesTotal, MapFoldingState
2
2
  from mapFolding.syntheticModules.theorem2Numba import count
3
3
 
4
- def doTheNeedful(state: MapFoldingState) -> MapFoldingState:
4
+ def sequential(state: MapFoldingState) -> MapFoldingState:
5
5
  mapShape: tuple[DatatypeLeavesTotal, ...] = state.mapShape
6
6
  groupsOfFolds: DatatypeFoldsTotal = state.groupsOfFolds
7
7
  gap1ndex: DatatypeElephino = state.gap1ndex
mapFolding/theSSOT.py CHANGED
@@ -1,268 +1,34 @@
1
- """
2
- Single Source of Truth module for configuration, types, and computational state management.
3
-
4
- This module defines the core data structures, type definitions, and configuration settings used throughout the
5
- mapFolding package. It implements the Single Source of Truth (SSOT) principle to ensure consistency across the package's
6
- components.
7
-
8
- Key features:
9
- 1. The `ComputationState` dataclass, which encapsulates the state of the folding computation.
10
- 2. Unified type definitions for integers and arrays used in the computation.
11
- 3. Configuration settings for synthetic module generation and dispatching.
12
- 4. Path resolution and management for package resources and job output.
13
- 5. Dynamic dispatch functionality for algorithm implementations.
14
-
15
- The module differentiates between "the" identifiers (package defaults) and other identifiers to avoid namespace
16
- collisions when transforming algorithms.
17
- """
18
-
19
- from collections.abc import Callable
20
1
  from importlib import import_module as importlib_import_module
21
2
  from inspect import getfile as inspect_getfile
22
3
  from pathlib import Path
23
4
  from tomli import load as tomli_load
24
- from types import ModuleType
25
5
  import dataclasses
26
- from mapFolding.datatypes import (
27
- Array1DElephino as Array1DElephino,
28
- Array1DFoldsTotal as Array1DFoldsTotal,
29
- Array1DLeavesTotal as Array1DLeavesTotal,
30
- Array3D as Array3D,
31
- DatatypeElephino as DatatypeElephino,
32
- DatatypeFoldsTotal as DatatypeFoldsTotal,
33
- DatatypeLeavesTotal as DatatypeLeavesTotal,
34
- NumPyElephino as NumPyElephino,
35
- NumPyFoldsTotal as NumPyFoldsTotal,
36
- NumPyIntegerType as NumPyIntegerType,
37
- NumPyLeavesTotal as NumPyLeavesTotal,
38
- )
39
6
 
40
- # Evaluate When Packaging https://github.com/hunterhogan/mapFolding/issues/18
7
+ packageNamePACKAGING_HARDCODED = "mapFolding"
8
+ concurrencyPackageHARDCODED = 'multiprocessing'
9
+
10
+ # Evaluate When Packaging
11
+ # https://github.com/hunterhogan/mapFolding/issues/18
41
12
  try:
42
13
  packageNamePACKAGING: str = tomli_load(Path("../pyproject.toml").open('rb'))["project"]["name"]
43
14
  except Exception:
44
- packageNamePACKAGING = "mapFolding"
15
+ packageNamePACKAGING = packageNamePACKAGING_HARDCODED
45
16
 
46
- # Evaluate When Installing https://github.com/hunterhogan/mapFolding/issues/18
17
+ # Evaluate When Installing
18
+ # https://github.com/hunterhogan/mapFolding/issues/18
47
19
  def getPathPackageINSTALLING() -> Path:
48
20
  pathPackage: Path = Path(inspect_getfile(importlib_import_module(packageNamePACKAGING)))
49
21
  if pathPackage.is_file():
50
22
  pathPackage = pathPackage.parent
51
23
  return pathPackage
52
24
 
53
- # I believe these values should be dynamically determined, so I have conspicuously marked them "HARDCODED"
54
- # and created downstream logic that assumes the values were dynamically determined.
55
- # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
56
- logicalPathModuleDispatcherHARDCODED: str = 'mapFolding.syntheticModules.numbaCount'
57
- callableDispatcherHARDCODED: str = 'doTheNeedful'
58
- concurrencyPackageHARDCODED = 'multiprocessing'
59
- # from mapFolding.someAssemblyRequired.toolboxNumba.theNumbaFlow
60
-
61
- # PackageSettings in theSSOT.py and immutability https://github.com/hunterhogan/mapFolding/issues/11
62
25
  @dataclasses.dataclass
63
26
  class PackageSettings:
64
- """
65
- Centralized configuration settings for the mapFolding package.
66
-
67
- This class implements the Single Source of Truth (SSOT) principle for package configuration, providing a consistent
68
- interface for accessing package settings, paths, and dispatch functions. The primary instance of this class, named
69
- `The`, is imported and used throughout the package to retrieve configuration values.
70
- """
71
-
72
- logicalPathModuleDispatcher: str | None = None
73
- """Logical import path to the module containing the dispatcher function."""
74
-
75
- callableDispatcher: str | None = None
76
- """Name of the function within the dispatcher module that will be called."""
77
-
78
- concurrencyPackage: str | None = None
79
- """Package to use for concurrent execution (e.g., 'multiprocessing', 'numba')."""
80
-
81
- # "Evaluate When Packaging" and "Evaluate When Installing" https://github.com/hunterhogan/mapFolding/issues/18
82
- dataclassIdentifier: str = dataclasses.field(default='ComputationState', metadata={'evaluateWhen': 'packaging'})
83
- """Name of the dataclass used to track computation state."""
84
-
85
- dataclassInstance: str = dataclasses.field(default='state', metadata={'evaluateWhen': 'packaging'})
86
- """Default variable name for instances of the computation state dataclass."""
87
-
88
- dataclassInstanceTaskDistributionSuffix: str = dataclasses.field(default='Parallel', metadata={'evaluateWhen': 'packaging'})
89
- """Suffix added to dataclassInstance for parallel task distribution."""
90
-
91
- dataclassModule: str = dataclasses.field(default='theSSOT', metadata={'evaluateWhen': 'packaging'})
92
- """Module containing the computation state dataclass definition."""
93
-
94
- datatypePackage: str = dataclasses.field(default='numpy', metadata={'evaluateWhen': 'packaging'})
95
- """Package providing the numeric data types used in computation."""
96
-
97
27
  fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
98
- """Default file extension for generated code files."""
99
-
100
28
  packageName: str = dataclasses.field(default = packageNamePACKAGING, metadata={'evaluateWhen': 'packaging'})
101
- """Name of this package, used for import paths and configuration."""
102
-
103
29
  pathPackage: Path = dataclasses.field(default_factory=getPathPackageINSTALLING, metadata={'evaluateWhen': 'installing'})
104
- """Absolute path to the installed package directory."""
105
-
106
- sourceAlgorithm: str = dataclasses.field(default='theDao', metadata={'evaluateWhen': 'packaging'})
107
- """Module containing the reference implementation of the algorithm."""
108
-
109
- sourceCallableDispatcher: str = dataclasses.field(default='doTheNeedful', metadata={'evaluateWhen': 'packaging'})
110
- """Name of the function that dispatches computation in the source algorithm."""
111
-
112
- sourceCallableInitialize: str = dataclasses.field(default='countInitialize', metadata={'evaluateWhen': 'packaging'})
113
- """Name of the function that initializes computation in the source algorithm."""
114
-
115
- sourceCallableParallel: str = dataclasses.field(default='countParallel', metadata={'evaluateWhen': 'packaging'})
116
- """Name of the function that performs parallel computation in the source algorithm."""
117
-
118
- sourceCallableSequential: str = dataclasses.field(default='countSequential', metadata={'evaluateWhen': 'packaging'})
119
- """Name of the function that performs sequential computation in the source algorithm."""
120
-
121
- sourceConcurrencyManagerIdentifier: str = dataclasses.field(default='submit', metadata={'evaluateWhen': 'packaging'})
122
- """Method name used to submit tasks to the concurrency manager."""
123
-
124
- sourceConcurrencyManagerNamespace: str = dataclasses.field(default='concurrencyManager', metadata={'evaluateWhen': 'packaging'})
125
- """Variable name used for the concurrency manager instance."""
126
-
127
- sourceConcurrencyPackage: str = dataclasses.field(default='multiprocessing', metadata={'evaluateWhen': 'packaging'})
128
- """Default package used for concurrency in the source algorithm."""
129
-
130
- dataclassInstanceTaskDistribution: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
131
- """Variable name for the parallel distribution instance of the computation state."""
132
-
133
- logicalPathModuleDataclass: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
134
- """Fully qualified import path to the module containing the computation state dataclass."""
135
-
136
- logicalPathModuleSourceAlgorithm: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
137
- """Fully qualified import path to the module containing the source algorithm."""
138
-
139
- @property
140
- def dispatcher(self) -> Callable[['ComputationState'], 'ComputationState']:
141
- """ _The_ callable that connects `countFolds` to the logic that does the work."""
142
- logicalPath: str = self.logicalPathModuleDispatcher or self.logicalPathModuleSourceAlgorithm
143
- identifier: str = self.callableDispatcher or self.sourceCallableDispatcher
144
- moduleImported: ModuleType = importlib_import_module(logicalPath)
145
- return getattr(moduleImported, identifier)
146
-
147
- def __post_init__(self) -> None:
148
- if self.dataclassInstanceTaskDistribution is None: # pyright: ignore[reportUnnecessaryComparison]
149
- self.dataclassInstanceTaskDistribution = self.dataclassInstance + self.dataclassInstanceTaskDistributionSuffix
150
-
151
- if self.logicalPathModuleDataclass is None: # pyright: ignore[reportUnnecessaryComparison]
152
- self.logicalPathModuleDataclass = '.'.join([self.packageName, self.dataclassModule])
153
- if self.logicalPathModuleSourceAlgorithm is None: # pyright: ignore[reportUnnecessaryComparison]
154
- self.logicalPathModuleSourceAlgorithm = '.'.join([self.packageName, self.sourceAlgorithm])
155
-
156
- The = PackageSettings(logicalPathModuleDispatcher=logicalPathModuleDispatcherHARDCODED, callableDispatcher=callableDispatcherHARDCODED, concurrencyPackage=concurrencyPackageHARDCODED)
157
-
158
- @dataclasses.dataclass
159
- class ComputationState:
160
- """
161
- Represents the complete state of a map folding computation.
162
-
163
- This dataclass encapsulates all the information required to compute the number of possible ways to fold a map,
164
- including the map dimensions, leaf connections, computation progress, and fold counting. It serves as the central
165
- data structure that flows through the entire computational algorithm.
166
-
167
- Fields are categorized into:
168
- 1. Input parameters (`mapShape`, `leavesTotal`, etc.).
169
- 2. Core computational structures (`connectionGraph`, etc.).
170
- 3. Tracking variables for the folding algorithm state.
171
- 4. Result accumulation fields (`foldsTotal`, `groupsOfFolds`).
172
- """
173
- # NOTE Python is anti-DRY, again, `DatatypeLeavesTotal` metadata needs to match the type
174
- mapShape: tuple[DatatypeLeavesTotal, ...] = dataclasses.field(init=True, metadata={'elementConstructor': 'DatatypeLeavesTotal'})
175
- """Dimensions of the map to be folded, as a tuple of integers."""
176
-
177
- leavesTotal: DatatypeLeavesTotal
178
- """Total number of leaves (unit squares) in the map, equal to the product of all dimensions."""
179
-
180
- taskDivisions: DatatypeLeavesTotal
181
- """Number of parallel tasks to divide the computation into. Zero means sequential computation."""
182
-
183
- concurrencyLimit: DatatypeElephino
184
- """Maximum number of concurrent processes to use during computation."""
185
-
186
- connectionGraph: Array3D = dataclasses.field(init=False, metadata={'dtype': Array3D.__args__[1].__args__[0]}) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
187
- """3D array encoding the connections between leaves in all dimensions."""
188
-
189
- dimensionsTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
190
- """Total number of dimensions in the map shape."""
191
-
192
- # I am using `dataclasses.field` metadata and `typeAlias.__args__[1].__args__[0]` to make the code more DRY. https://github.com/hunterhogan/mapFolding/issues/9
193
- countDimensionsGapped: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
194
- """Tracks how many dimensions are gapped for each leaf."""
195
-
196
- dimensionsUnconstrained: DatatypeLeavesTotal = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
197
- """Number of dimensions that are not constrained in the current folding state."""
198
-
199
- gapRangeStart: Array1DElephino = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DElephino.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
200
- """Starting index for the gap range for each leaf."""
201
-
202
- gapsWhere: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
203
- """Tracks where gaps occur in the folding pattern."""
204
-
205
- leafAbove: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
206
- """For each leaf, stores the index of the leaf above it in the folding pattern."""
207
-
208
- leafBelow: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
209
- """For each leaf, stores the index of the leaf below it in the folding pattern."""
210
-
211
- foldGroups: Array1DFoldsTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DFoldsTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
212
- """Accumulator for fold groups across parallel tasks."""
213
-
214
- foldsTotal: DatatypeFoldsTotal = DatatypeFoldsTotal(0)
215
- """The final computed total number of distinct folding patterns."""
216
-
217
- gap1ndex: DatatypeElephino = DatatypeElephino(0)
218
- """Current index into gaps array during algorithm execution."""
219
-
220
- gap1ndexCeiling: DatatypeElephino = DatatypeElephino(0)
221
- """Upper limit for gap index during the current algorithm phase."""
222
-
223
- groupsOfFolds: DatatypeFoldsTotal = dataclasses.field(default=DatatypeFoldsTotal(0), metadata={'theCountingIdentifier': True})
224
- """Accumulator for the number of fold groups found during computation."""
225
-
226
- indexDimension: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
227
- """Current dimension being processed during algorithm execution."""
228
-
229
- indexLeaf: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
230
- """Current leaf index during iteration."""
231
-
232
- indexMiniGap: DatatypeElephino = DatatypeElephino(0)
233
- """Index used when filtering common gaps."""
234
-
235
- leaf1ndex: DatatypeLeavesTotal = DatatypeLeavesTotal(1)
236
- """Active leaf being processed in the folding algorithm. Starts at 1, not 0."""
237
-
238
- leafConnectee: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
239
- """Leaf that is being connected to the active leaf."""
240
-
241
- taskIndex: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
242
- """Index of the current parallel task when using task divisions."""
243
-
244
- def __post_init__(self) -> None:
245
- from mapFolding.beDRY import getConnectionGraph, makeDataContainer
246
- self.dimensionsTotal = DatatypeLeavesTotal(len(self.mapShape))
247
- leavesTotalAsInt = int(self.leavesTotal)
248
- self.connectionGraph = getConnectionGraph(self.mapShape, leavesTotalAsInt, self.__dataclass_fields__['connectionGraph'].metadata['dtype'])
249
-
250
- if self.dimensionsUnconstrained is None: self.dimensionsUnconstrained = DatatypeLeavesTotal(int(self.dimensionsTotal)) # pyright: ignore[reportUnnecessaryComparison]
251
-
252
- if self.foldGroups is None: # pyright: ignore[reportUnnecessaryComparison]
253
- self.foldGroups = makeDataContainer(max(2, int(self.taskDivisions) + 1), self.__dataclass_fields__['foldGroups'].metadata['dtype'])
254
- self.foldGroups[-1] = self.leavesTotal
255
-
256
- # Dataclasses, Default factories, and arguments in `ComputationState` https://github.com/hunterhogan/mapFolding/issues/12
257
- if self.gapsWhere is None: self.gapsWhere = makeDataContainer(leavesTotalAsInt * leavesTotalAsInt + 1, self.__dataclass_fields__['gapsWhere'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
258
-
259
- if self.countDimensionsGapped is None: self.countDimensionsGapped = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['countDimensionsGapped'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
260
- if self.gapRangeStart is None: self.gapRangeStart = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['gapRangeStart'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
261
- if self.leafAbove is None: self.leafAbove = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafAbove'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
262
- if self.leafBelow is None: self.leafBelow = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafBelow'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
263
-
264
- # Automatic, or not, calculation in dataclass `ComputationState` https://github.com/hunterhogan/mapFolding/issues/14
265
- def getFoldsTotal(self) -> None:
266
- self.foldsTotal = DatatypeFoldsTotal(self.foldGroups[0:-1].sum() * self.leavesTotal)
30
+ concurrencyPackage: str | None = None
31
+ """Package to use for concurrent execution (e.g., 'multiprocessing', 'numba')."""
267
32
 
268
- class raiseIfNoneGitHubIssueNumber3(Exception): pass
33
+ concurrencyPackage = concurrencyPackageHARDCODED
34
+ packageSettings = PackageSettings(concurrencyPackage=concurrencyPackage)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapFolding
3
- Version: 0.11.1
3
+ Version: 0.11.3
4
4
  Summary: Map folding algorithm with code transformation framework for optimizing numerical computations
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
@@ -33,8 +33,6 @@ Description-Content-Type: text/markdown
33
33
  License-File: LICENSE
34
34
  Requires-Dist: astToolkit
35
35
  Requires-Dist: autoflake
36
- Requires-Dist: cytoolz
37
- Requires-Dist: more_itertools
38
36
  Requires-Dist: numba_progress
39
37
  Requires-Dist: numba
40
38
  Requires-Dist: numpy
@@ -42,7 +40,6 @@ Requires-Dist: platformdirs
42
40
  Requires-Dist: python_minifier
43
41
  Requires-Dist: sympy
44
42
  Requires-Dist: tomli
45
- Requires-Dist: typeshed_client
46
43
  Requires-Dist: Z0Z_tools
47
44
  Provides-Extra: testing
48
45
  Requires-Dist: mypy; extra == "testing"
@@ -67,12 +64,6 @@ Dynamic: license-file
67
64
  - You're interested in solving mathematical puzzles through code
68
65
  - You're learning about Numba and advanced Python optimization
69
66
 
70
- **This package is NOT for you if:**
71
-
72
- - You're looking for a general-purpose folding simulation tool
73
- - You need commercial-ready mapping software
74
- - You want simple visualization of folding patterns
75
-
76
67
  ## What Does This Package Actually Do?
77
68
 
78
69
  `mapFolding` solves a specific mathematical problem: counting the number of distinct ways to fold a rectangular map. While this may sound niche, it's a fascinating computational challenge that demonstrates:
@@ -87,7 +78,7 @@ The package has achieved new computational records, including first-ever calcula
87
78
  ```python
88
79
  # Compute the number of ways to fold a 5×5 grid:
89
80
  from mapFolding import oeisIDfor_n
90
- foldsTotal = oeisIDfor_n('A001418', 5) # Returns 186,086,600
81
+ foldsTotal = oeisIDfor_n('A001418', 5)
91
82
  ```
92
83
 
93
84
  ## Key Benefits for Computational Enthusiasts
@@ -178,4 +169,18 @@ If you've read this far and are intrigued by computational puzzles, algorithm op
178
169
 
179
170
  Whether you use it to solve map folding problems or to study its optimization techniques, `mapFolding` offers a unique window into advanced Python programming approaches.
180
171
 
172
+ ## My recovery
173
+
174
+ [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
175
+ [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
176
+
177
+ ## How to code
178
+
179
+ Coding One Step at a Time:
180
+
181
+ 0. WRITE CODE.
182
+ 1. Don't write stupid code that's hard to revise.
183
+ 2. Write good code.
184
+ 3. When revising, write better code.
185
+
181
186
  [![CC-BY-NC-4.0](https://github.com/hunterhogan/mapFolding/blob/main/CC-BY-NC-4.0.svg)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -0,0 +1,53 @@
1
+ mapFolding/__init__.py,sha256=5aecz_WZCLtBsArV68h30OEQxrylPPm0oVfQMt0N94A,1948
2
+ mapFolding/basecamp.py,sha256=3MMWSN8eW4W_dZe_q7Vs5yqmazNFoml0VpcJNY2feho,8243
3
+ mapFolding/beDRY.py,sha256=kQrgBhJkXEy-J-Zyu4RHILK6gRm72a7cON7a1LewgwI,14004
4
+ mapFolding/daoOfMapFolding.py,sha256=ncTIiBfTsM8SNVx9qefZ0bBcBtviWLSk4iPv3Z9nGiE,5442
5
+ mapFolding/dataBaskets.py,sha256=crfmmYJGeJ7QLCzuYi4rtVOtzCDRoOiTNfPfHbc6Foo,5620
6
+ mapFolding/datatypes.py,sha256=dqOAa2RbiGcsRl9X4qo4tdMamgOoZVnewrMjY4mHXS4,773
7
+ mapFolding/filesystemToolkit.py,sha256=O9VQ0tSXlrGUhU3qN7uWxOTAZfuQb3fcRkTrfRZrGXo,9854
8
+ mapFolding/oeis.py,sha256=ifs9Uu4uqgKCd46aHuCqhyXt2JXLMcPmmWRFuXIBACg,16926
9
+ mapFolding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ mapFolding/theSSOT.py,sha256=7CyB3FnDWyNovTuUPRvSIfF7GxQc3aZoIsIwF1-4OyE,1456
11
+ mapFolding/reference/__init__.py,sha256=GKcSgYE49NcTISx-JZbELXyq-eRkMeTL5g4DXInWFw0,2206
12
+ mapFolding/reference/flattened.py,sha256=QK1xG9SllqCoi68e86Hyl9d9ATUAAFNpTQI-3zmcp5I,16072
13
+ mapFolding/reference/hunterNumba.py,sha256=iLfyqwGdAh6c5GbapnKsWhAsNsR3O-fyGGHAdohluLw,7258
14
+ mapFolding/reference/irvineJavaPort.py,sha256=UEfIX4QbPLl5jnyfYIyX5YRR3_rYvPUikK8jLehsFko,4076
15
+ mapFolding/reference/jaxCount.py,sha256=TuDNKOnyhQfuixKmIxO9Algv7dvy7KMGhgsV3h96FGE,14853
16
+ mapFolding/reference/lunnonNumpy.py,sha256=mMgrgbrBpe4nmo72ThEI-MGH0OwEHmfMPczSXHp2qKo,4357
17
+ mapFolding/reference/lunnonWhile.py,sha256=ZL8GAQtPs5nJZSgoDl5USrLSS_zs03y98y1Z9E4jOmQ,3799
18
+ mapFolding/reference/rotatedEntryPoint.py,sha256=5ughpKUT2JQhoAKgoDUdYNjgWQYPGV8v-7dWEAdDmfE,10274
19
+ mapFolding/reference/total_countPlus1vsPlusN.py,sha256=yJZAVLVdoXqHag2_N6_6CT-Q6HXBgRro-eny93-Rlpw,9307
20
+ mapFolding/reference/jobsCompleted/__init__.py,sha256=TU93ZGUW1xEkT6d9mQFn_rp5DvRy0ZslEB2Q6MF5ZDc,2596
21
+ mapFolding/reference/jobsCompleted/[2x19]/p2x19.py,sha256=_tvYtfzMWVo2VtUbIAieoscb4N8FFflgTdW4-ljBUuA,19626
22
+ mapFolding/reference/jobsCompleted/p2x19/p2x19.py,sha256=eZEw4Me4ocTt6VXoK2-Sbd5SowZtxRIbN9dZmc7OCVg,6395
23
+ mapFolding/someAssemblyRequired/RecipeJob.py,sha256=H53d1pIxh6kvEsh9Nmj7bD8agMBbpHziY2XrN808eGc,4432
24
+ mapFolding/someAssemblyRequired/Z0Z_makeAllModules.py,sha256=7REamiISEiZQ_goseQB2BJW4kMb4wRhoJnqhIRdX_zI,30262
25
+ mapFolding/someAssemblyRequired/__init__.py,sha256=Wq8rrFJzZsQV4oYQmDXSlDtAlU4TRgMc5o0EKJedvGE,2705
26
+ mapFolding/someAssemblyRequired/_toolIfThis.py,sha256=nq7brHOA4JOK9P-RWyOFae6QxzX40nV46DNDrNmkLYo,3122
27
+ mapFolding/someAssemblyRequired/_toolkitContainers.py,sha256=C-GkXSYoneWmcJWULPa5bgLidjjcIFbrEtEpnrK1m_Y,9784
28
+ mapFolding/someAssemblyRequired/getLLVMforNoReason.py,sha256=9RPU6vK_eUg64GtVFI_nZnvUryXw8gfHJs9NyDYHIvg,2745
29
+ mapFolding/someAssemblyRequired/infoBooth.py,sha256=IXzpt_YKkWfOOiaQaBh61SiEd-TnLRlHifmq_1mCow4,4249
30
+ mapFolding/someAssemblyRequired/makeJobTheorem2Numba.py,sha256=6bKLNI9po2qS1_JsKr4wnqwY1A5eL5LQN9uLu3jTlkM,14039
31
+ mapFolding/someAssemblyRequired/toolkitNumba.py,sha256=c-rq2rY-Vr0X4asnGy2cmmKTJonv2Jh_mw3oWHT1ctI,6649
32
+ mapFolding/someAssemblyRequired/transformationTools.py,sha256=ysvZaP8XqxXjHtSHlaErNDGHI5OrRRcqVhnzs3F8UUA,8256
33
+ mapFolding/syntheticModules/__init__.py,sha256=evVFqhCGa-WZKDiLcnQWjs-Bj34eRnfSLqz_d7dFYZY,83
34
+ mapFolding/syntheticModules/countParallel.py,sha256=OK_IB9w4yy9MMAiGvkei5ezPm_00v2nYjPrQZ_IlELg,7733
35
+ mapFolding/syntheticModules/daoOfMapFolding.py,sha256=cfWPABtXyCxJ0BwPI7rhfLh_2UYV_XKAL8lJ4GLNXaQ,5896
36
+ mapFolding/syntheticModules/dataPacking.py,sha256=m_eOZ7sMXIQ9jY5EvC3qgitQTY60n6rksy0ACMJOIC8,2292
37
+ mapFolding/syntheticModules/initializeCount.py,sha256=nWSlJMMfIM3DvZxMn6ISQusUJqRYAjKQyLF5hwLEdBQ,3119
38
+ mapFolding/syntheticModules/theorem2.py,sha256=9jrbZNNX4BWYZW1S0JjvRY2k7RU7I1RNUMV7JdCt1ZY,3017
39
+ mapFolding/syntheticModules/theorem2Numba.py,sha256=-cKjNyxgUMFhEyFVs0VJ7hw4LfrV0WSNK5tPYbQ1oNU,3369
40
+ mapFolding/syntheticModules/theorem2Trimmed.py,sha256=DHW3NxBdtABQYBKm2WRvfQ5kzc2_UwGI2h4ePuYEJoM,2685
41
+ mapfolding-0.11.3.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
42
+ tests/__init__.py,sha256=5VhHf0JJ2_DSh58zJ0rR5UkpoCon-0IkdljspTCzZ04,1950
43
+ tests/conftest.py,sha256=eumQRoDuWVrhsjDxWXGhW0N7lH0ZZ9XD-5q81bWFqOs,10874
44
+ tests/test_computations.py,sha256=HNpfs9Yz3rdfJInD15Jwd6DYsSR5TCwnR5EW0n7KbeI,5682
45
+ tests/test_filesystem.py,sha256=imlcetleJc4G9pDZTgS1j8UAs7ADbRxXVuNPecJAvqc,2964
46
+ tests/test_oeis.py,sha256=uxvwmgbnylSDdsVJfuAT0LuYLbIVFwSgdLxHm-xUGBM,5043
47
+ tests/test_other.py,sha256=DT7YE82YCHrSjdxhpY4UJnXmZvDm1b4e1QpZV3LyzcM,3747
48
+ tests/test_tasks.py,sha256=pEDukf2SVTOMEsn82JpAWKQzn1ZCTlkhLzQ5hYLg2yY,2780
49
+ mapfolding-0.11.3.dist-info/METADATA,sha256=FyB0PTluboy3BsrccAJJGrEhGpo5nUsbd3JJe6CAsXo,7712
50
+ mapfolding-0.11.3.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
51
+ mapfolding-0.11.3.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
52
+ mapfolding-0.11.3.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
53
+ mapfolding-0.11.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.0.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/conftest.py CHANGED
@@ -56,15 +56,10 @@ See the examples in `test_computations.py` for guidance on adapting these fixtur
56
56
  """
57
57
 
58
58
  from collections.abc import Callable, Generator, Sequence
59
- from mapFolding import The
60
- from mapFolding.beDRY import getLeavesTotal, validateListDimensions, makeDataContainer
59
+ from mapFolding import getLeavesTotal, validateListDimensions, makeDataContainer
61
60
  from mapFolding.oeis import oeisIDsImplemented, settingsOEIS
62
- from mapFolding.someAssemblyRequired import importLogicalPath2Callable, RecipeSynthesizeFlow
63
- from pathlib import Path, PurePosixPath
64
- from types import ModuleType
61
+ from pathlib import Path
65
62
  from typing import Any
66
- import importlib
67
- import importlib.util
68
63
  import numpy
69
64
  import pytest
70
65
  import random
@@ -239,78 +234,6 @@ def oeisID_1random() -> str:
239
234
  """Return one random valid OEIS ID."""
240
235
  return random.choice(oeisIDsImplemented)
241
236
 
242
- @pytest.fixture
243
- def useThisDispatcher() -> Generator[Callable[..., None], Any, None]:
244
- """A fixture providing a context manager for temporarily replacing the dispatcher.
245
-
246
- Returns
247
- A context manager for patching the dispatcher
248
- """
249
- import mapFolding.theSSOT as theSSOT
250
- from mapFolding.theSSOT import The
251
-
252
- # Store original property method
253
- original_dispatcher_property = theSSOT.PackageSettings.dispatcher
254
-
255
- def patchDispatcher(callableTarget: Callable[..., Any]) -> None:
256
- """Patch the dispatcher property to return the target callable."""
257
- # Create a new property that returns the target callable
258
- def patched_dispatcher(self: theSSOT.PackageSettings) -> Callable[..., Any]:
259
- def wrapper(state: Any) -> Any:
260
- return callableTarget(state)
261
- return wrapper
262
-
263
- # Replace the property with our patched version
264
- theSSOT.PackageSettings.dispatcher = property(patched_dispatcher) # type: ignore
265
-
266
- yield patchDispatcher
267
-
268
- # Restore the original property
269
- theSSOT.PackageSettings.dispatcher = original_dispatcher_property # type: ignore
270
-
271
- def getAlgorithmDispatcher() -> Callable[..., Any]:
272
- moduleImported: ModuleType = importlib.import_module(The.logicalPathModuleSourceAlgorithm)
273
- dispatcherCallable = getattr(moduleImported, The.sourceCallableDispatcher)
274
- return dispatcherCallable
275
-
276
- @pytest.fixture
277
- def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Generator[None, None, None]:
278
- """Temporarily patches getDispatcherCallable to return the algorithm dispatcher."""
279
- useThisDispatcher(importLogicalPath2Callable(The.logicalPathModuleSourceAlgorithm, The.sourceCallableDispatcher))
280
- yield
281
-
282
- @pytest.fixture
283
- def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any], pathTmpTesting: Path) -> Callable[..., Any]:
284
- """Generate synthetic Numba-optimized dispatcher module and patch the dispatcher"""
285
- from mapFolding.someAssemblyRequired.toolboxNumba import makeNumbaFlow
286
-
287
- TESTINGrecipeFlow = RecipeSynthesizeFlow(
288
- pathPackage=PurePosixPath(pathTmpTesting.absolute()),
289
- logicalPathFlowRoot=None,
290
- moduleDispatcher="test_dispatcher",
291
- # Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
292
- # dispatcherCallable="dispatcherSynthetic",
293
- )
294
-
295
- # Generate optimized module in test directory
296
- makeNumbaFlow(TESTINGrecipeFlow)
297
-
298
- # Import synthesized dispatcher
299
- importlibSpecificationDispatcher = importlib.util.spec_from_file_location(
300
- TESTINGrecipeFlow.moduleDispatcher,
301
- Path(TESTINGrecipeFlow.pathFilenameDispatcher),
302
- )
303
- if importlibSpecificationDispatcher is None or importlibSpecificationDispatcher.loader is None:
304
- raise ImportError("Failed to load synthetic dispatcher module")
305
-
306
- moduleSpecificationDispatcher = importlib.util.module_from_spec(importlibSpecificationDispatcher)
307
- importlibSpecificationDispatcher.loader.exec_module(moduleSpecificationDispatcher)
308
- callableDispatcherSynthetic = getattr(moduleSpecificationDispatcher, TESTINGrecipeFlow.callableDispatcher)
309
-
310
- # Patch dispatcher and return callable
311
- useThisDispatcher(callableDispatcherSynthetic)
312
- return callableDispatcherSynthetic
313
-
314
237
  def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
315
238
  """Format assertion message for any test comparison."""
316
239
  return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"