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.
- mapFolding/__init__.py +66 -18
- mapFolding/basecamp.py +32 -17
- mapFolding/beDRY.py +3 -3
- mapFolding/oeis.py +121 -25
- mapFolding/someAssemblyRequired/__init__.py +48 -27
- mapFolding/someAssemblyRequired/_theTypes.py +11 -15
- mapFolding/someAssemblyRequired/_tool_Make.py +40 -12
- mapFolding/someAssemblyRequired/_tool_Then.py +59 -25
- mapFolding/someAssemblyRequired/_toolboxAntecedents.py +151 -276
- mapFolding/someAssemblyRequired/_toolboxContainers.py +185 -51
- mapFolding/someAssemblyRequired/_toolboxPython.py +165 -44
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +141 -20
- mapFolding/someAssemblyRequired/toolboxNumba.py +93 -52
- mapFolding/someAssemblyRequired/transformationTools.py +228 -138
- mapFolding/syntheticModules/numbaCount_doTheNeedful.py +0 -1
- mapFolding/theSSOT.py +147 -55
- mapFolding/toolboxFilesystem.py +1 -1
- mapfolding-0.9.0.dist-info/METADATA +177 -0
- mapfolding-0.9.0.dist-info/RECORD +46 -0
- tests/__init__.py +44 -0
- tests/conftest.py +75 -7
- tests/test_computations.py +90 -9
- tests/test_filesystem.py +32 -33
- tests/test_other.py +0 -1
- tests/test_tasks.py +2 -2
- mapFolding/noHomeYet.py +0 -32
- mapFolding/someAssemblyRequired/newInliner.py +0 -22
- mapfolding-0.8.5.dist-info/METADATA +0 -190
- mapfolding-0.8.5.dist-info/RECORD +0 -48
- {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/WHEEL +0 -0
- {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/licenses/LICENSE +0 -0
- {mapfolding-0.8.5.dist-info → mapfolding-0.9.0.dist-info}/top_level.txt +0 -0
mapFolding/theSSOT.py
CHANGED
|
@@ -26,67 +26,107 @@ from types import ModuleType
|
|
|
26
26
|
from typing import Any, TypeAlias, TypeVar
|
|
27
27
|
import dataclasses
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
# The Wrong Way: Evaluate When Packaging
|
|
31
|
-
|
|
29
|
+
# Evaluate When Packaging https://github.com/hunterhogan/mapFolding/issues/18
|
|
32
30
|
try:
|
|
33
31
|
packageNamePACKAGING: str = tomli_load(Path("../pyproject.toml").open('rb'))["project"]["name"]
|
|
34
32
|
except Exception:
|
|
35
33
|
packageNamePACKAGING = "mapFolding"
|
|
36
34
|
|
|
37
|
-
#
|
|
38
|
-
|
|
35
|
+
# Evaluate When Installing https://github.com/hunterhogan/mapFolding/issues/18
|
|
39
36
|
def getPathPackageINSTALLING() -> Path:
|
|
40
37
|
pathPackage: Path = Path(inspect_getfile(importlib_import_module(packageNamePACKAGING)))
|
|
41
38
|
if pathPackage.is_file():
|
|
42
39
|
pathPackage = pathPackage.parent
|
|
43
40
|
return pathPackage
|
|
44
41
|
|
|
45
|
-
#
|
|
46
|
-
#
|
|
42
|
+
# I believe these values should be dynamically determined, so I have conspicuously marked them "HARDCODED"
|
|
43
|
+
# and created downstream logic that assumes the values were dynamically determined.
|
|
47
44
|
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
48
|
-
|
|
49
|
-
# from mapFolding.someAssemblyRequired.synthesizeNumbaFlow.theNumbaFlow
|
|
50
45
|
logicalPathModuleDispatcherHARDCODED: str = 'mapFolding.syntheticModules.numbaCount_doTheNeedful'
|
|
51
46
|
callableDispatcherHARDCODED: str = 'doTheNeedful'
|
|
52
47
|
concurrencyPackageHARDCODED = 'multiprocessing'
|
|
48
|
+
# from mapFolding.someAssemblyRequired.synthesizeNumbaFlow.theNumbaFlow
|
|
53
49
|
|
|
54
|
-
#
|
|
55
|
-
# The following is an improvement, but it is not the full solution.
|
|
56
|
-
# I hope that the standardized markers, `metadata={'evaluateWhen': 'packaging'}` will help to automate
|
|
57
|
-
# whatever needs to happen so that the following is well implemented.
|
|
58
|
-
# @dataclasses.dataclass(frozen=True)
|
|
50
|
+
# PackageSettings in theSSOT.py and immutability https://github.com/hunterhogan/mapFolding/issues/11
|
|
59
51
|
@dataclasses.dataclass
|
|
60
52
|
class PackageSettings:
|
|
53
|
+
"""
|
|
54
|
+
Centralized configuration settings for the mapFolding package.
|
|
55
|
+
|
|
56
|
+
This class implements the Single Source of Truth (SSOT) principle for package
|
|
57
|
+
configuration, providing a consistent interface for accessing package settings,
|
|
58
|
+
paths, and dispatch functions. The primary instance of this class, named `The`,
|
|
59
|
+
is imported and used throughout the package to retrieve configuration values.
|
|
60
|
+
"""
|
|
61
61
|
|
|
62
62
|
logicalPathModuleDispatcher: str | None = None
|
|
63
|
+
"""Logical import path to the module containing the dispatcher function."""
|
|
64
|
+
|
|
63
65
|
callableDispatcher: str | None = None
|
|
64
|
-
|
|
66
|
+
"""Name of the function within the dispatcher module that will be called."""
|
|
67
|
+
|
|
68
|
+
concurrencyPackage: str | None = None
|
|
69
|
+
"""Package to use for concurrent execution (e.g., 'multiprocessing', 'numba')."""
|
|
70
|
+
|
|
71
|
+
# "Evaluate When Packaging" and "Evaluate When Installing" https://github.com/hunterhogan/mapFolding/issues/18
|
|
65
72
|
dataclassIdentifier: str = dataclasses.field(default='ComputationState', metadata={'evaluateWhen': 'packaging'})
|
|
73
|
+
"""Name of the dataclass used to track computation state."""
|
|
74
|
+
|
|
66
75
|
dataclassInstance: str = dataclasses.field(default='state', metadata={'evaluateWhen': 'packaging'})
|
|
76
|
+
"""Default variable name for instances of the computation state dataclass."""
|
|
77
|
+
|
|
67
78
|
dataclassInstanceTaskDistributionSuffix: str = dataclasses.field(default='Parallel', metadata={'evaluateWhen': 'packaging'})
|
|
79
|
+
"""Suffix added to dataclassInstance for parallel task distribution."""
|
|
80
|
+
|
|
68
81
|
dataclassModule: str = dataclasses.field(default='theSSOT', metadata={'evaluateWhen': 'packaging'})
|
|
82
|
+
"""Module containing the computation state dataclass definition."""
|
|
83
|
+
|
|
69
84
|
datatypePackage: str = dataclasses.field(default='numpy', metadata={'evaluateWhen': 'packaging'})
|
|
85
|
+
"""Package providing the numeric data types used in computation."""
|
|
86
|
+
|
|
70
87
|
fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
|
|
88
|
+
"""Default file extension for generated code files."""
|
|
89
|
+
|
|
71
90
|
packageName: str = dataclasses.field(default = packageNamePACKAGING, metadata={'evaluateWhen': 'packaging'})
|
|
72
|
-
|
|
91
|
+
"""Name of this package, used for import paths and configuration."""
|
|
92
|
+
|
|
93
|
+
pathPackage: Path = dataclasses.field(default_factory=getPathPackageINSTALLING, metadata={'evaluateWhen': 'installing'})
|
|
94
|
+
"""Absolute path to the installed package directory."""
|
|
95
|
+
|
|
73
96
|
sourceAlgorithm: str = dataclasses.field(default='theDao', metadata={'evaluateWhen': 'packaging'})
|
|
97
|
+
"""Module containing the reference implementation of the algorithm."""
|
|
98
|
+
|
|
74
99
|
sourceCallableDispatcher: str = dataclasses.field(default='doTheNeedful', metadata={'evaluateWhen': 'packaging'})
|
|
100
|
+
"""Name of the function that dispatches computation in the source algorithm."""
|
|
101
|
+
|
|
75
102
|
sourceCallableInitialize: str = dataclasses.field(default='countInitialize', metadata={'evaluateWhen': 'packaging'})
|
|
103
|
+
"""Name of the function that initializes computation in the source algorithm."""
|
|
104
|
+
|
|
76
105
|
sourceCallableParallel: str = dataclasses.field(default='countParallel', metadata={'evaluateWhen': 'packaging'})
|
|
106
|
+
"""Name of the function that performs parallel computation in the source algorithm."""
|
|
107
|
+
|
|
77
108
|
sourceCallableSequential: str = dataclasses.field(default='countSequential', metadata={'evaluateWhen': 'packaging'})
|
|
109
|
+
"""Name of the function that performs sequential computation in the source algorithm."""
|
|
110
|
+
|
|
78
111
|
sourceConcurrencyManagerIdentifier: str = dataclasses.field(default='submit', metadata={'evaluateWhen': 'packaging'})
|
|
112
|
+
"""Method name used to submit tasks to the concurrency manager."""
|
|
113
|
+
|
|
79
114
|
sourceConcurrencyManagerNamespace: str = dataclasses.field(default='concurrencyManager', metadata={'evaluateWhen': 'packaging'})
|
|
115
|
+
"""Variable name used for the concurrency manager instance."""
|
|
116
|
+
|
|
80
117
|
sourceConcurrencyPackage: str = dataclasses.field(default='multiprocessing', metadata={'evaluateWhen': 'packaging'})
|
|
118
|
+
"""Default package used for concurrency in the source algorithm."""
|
|
119
|
+
|
|
120
|
+
dataclassInstanceTaskDistribution: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
|
|
121
|
+
"""Variable name for the parallel distribution instance of the computation state."""
|
|
81
122
|
|
|
82
|
-
|
|
83
|
-
"""
|
|
84
|
-
logicalPathModuleDataclass: str = dataclasses.field(init=False)
|
|
85
|
-
""" The package.module.name logical path to the dataclass. """
|
|
86
|
-
logicalPathModuleSourceAlgorithm: str = dataclasses.field(init=False)
|
|
87
|
-
""" The package.module.name logical path to the source algorithm. """
|
|
123
|
+
logicalPathModuleDataclass: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
|
|
124
|
+
"""Fully qualified import path to the module containing the computation state dataclass."""
|
|
88
125
|
|
|
89
|
-
|
|
126
|
+
logicalPathModuleSourceAlgorithm: str = dataclasses.field(default=None, metadata={'evaluateWhen': 'packaging'}) # pyright: ignore[reportAssignmentType]
|
|
127
|
+
"""Fully qualified import path to the module containing the source algorithm."""
|
|
128
|
+
|
|
129
|
+
@property
|
|
90
130
|
def dispatcher(self) -> Callable[['ComputationState'], 'ComputationState']:
|
|
91
131
|
""" _The_ callable that connects `countFolds` to the logic that does the work."""
|
|
92
132
|
logicalPath: str = self.logicalPathModuleDispatcher or self.logicalPathModuleSourceAlgorithm
|
|
@@ -95,26 +135,20 @@ class PackageSettings:
|
|
|
95
135
|
return getattr(moduleImported, identifier)
|
|
96
136
|
|
|
97
137
|
def __post_init__(self) -> None:
|
|
98
|
-
self.dataclassInstanceTaskDistribution
|
|
138
|
+
if self.dataclassInstanceTaskDistribution is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
139
|
+
self.dataclassInstanceTaskDistribution = self.dataclassInstance + self.dataclassInstanceTaskDistributionSuffix
|
|
99
140
|
|
|
100
|
-
self.logicalPathModuleDataclass
|
|
101
|
-
|
|
141
|
+
if self.logicalPathModuleDataclass is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
142
|
+
self.logicalPathModuleDataclass = '.'.join([self.packageName, self.dataclassModule])
|
|
143
|
+
if self.logicalPathModuleSourceAlgorithm is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
144
|
+
self.logicalPathModuleSourceAlgorithm = '.'.join([self.packageName, self.sourceAlgorithm])
|
|
102
145
|
|
|
103
146
|
The = PackageSettings(logicalPathModuleDispatcher=logicalPathModuleDispatcherHARDCODED, callableDispatcher=callableDispatcherHARDCODED, concurrencyPackage=concurrencyPackageHARDCODED)
|
|
104
147
|
|
|
105
|
-
# To remove this function, I need to learn how to change "conftest.py" to patch this.
|
|
106
|
-
def getPackageDispatcher() -> Callable[['ComputationState'], 'ComputationState']:
|
|
107
|
-
"""Get the dispatcher callable for the package.
|
|
108
|
-
|
|
109
|
-
This function retrieves the dispatcher callable for the package based on the
|
|
110
|
-
logical path module and callable dispatcher defined in the PackageSettings.
|
|
111
|
-
"""
|
|
112
|
-
return The.dispatcher
|
|
113
148
|
# =============================================================================
|
|
114
149
|
# Flexible Data Structure System Needs Enhanced Paradigm https://github.com/hunterhogan/mapFolding/issues/9
|
|
115
|
-
# Efficient translation of Python scalar types to Numba types https://github.com/hunterhogan/mapFolding/issues/8
|
|
116
150
|
|
|
117
|
-
|
|
151
|
+
NumPyIntegerType = TypeVar('NumPyIntegerType', bound=integer[Any], covariant=True)
|
|
118
152
|
|
|
119
153
|
DatatypeLeavesTotal: TypeAlias = int
|
|
120
154
|
NumPyLeavesTotal: TypeAlias = numpy_int16 # this would be uint8, but mapShape (2,2,2,2, 2,2,2,2) has 256 leaves, so generic containers must accommodate at least 256 leaves
|
|
@@ -132,32 +166,92 @@ Array1DFoldsTotal: TypeAlias = ndarray[tuple[int], dtype[NumPyFoldsTotal]]
|
|
|
132
166
|
|
|
133
167
|
@dataclasses.dataclass
|
|
134
168
|
class ComputationState:
|
|
135
|
-
|
|
169
|
+
"""
|
|
170
|
+
Represents the complete state of a map folding computation.
|
|
171
|
+
|
|
172
|
+
This dataclass encapsulates all the information required to compute the number of
|
|
173
|
+
possible ways to fold a map, including the map dimensions, leaf connections,
|
|
174
|
+
computation progress, and fold counting. It serves as the central data structure
|
|
175
|
+
that flows through the entire computational algorithm.
|
|
176
|
+
|
|
177
|
+
Fields are categorized into:
|
|
178
|
+
1. Input parameters (mapShape, leavesTotal, etc.)
|
|
179
|
+
2. Core computational structures (connectionGraph, etc.)
|
|
180
|
+
3. Tracking variables for the folding algorithm state
|
|
181
|
+
4. Result accumulation fields (foldsTotal, groupsOfFolds)
|
|
182
|
+
|
|
183
|
+
The data structures and algorithms are based on Lunnon's 1971 paper on map folding.
|
|
184
|
+
"""
|
|
185
|
+
# NOTE Python is anti-DRY, again, `DatatypeLeavesTotal` metadata needs to match the type
|
|
186
|
+
mapShape: tuple[DatatypeLeavesTotal, ...] = dataclasses.field(init=True, metadata={'elementConstructor': 'DatatypeLeavesTotal'})
|
|
187
|
+
"""Dimensions of the map to be folded, as a tuple of integers."""
|
|
188
|
+
|
|
136
189
|
leavesTotal: DatatypeLeavesTotal
|
|
190
|
+
"""Total number of leaves (unit squares) in the map, equal to the product of all dimensions."""
|
|
191
|
+
|
|
137
192
|
taskDivisions: DatatypeLeavesTotal
|
|
193
|
+
"""Number of parallel tasks to divide the computation into. Zero means sequential computation."""
|
|
194
|
+
|
|
138
195
|
concurrencyLimit: DatatypeElephino
|
|
196
|
+
"""Maximum number of concurrent processes to use during computation."""
|
|
139
197
|
|
|
140
198
|
connectionGraph: Array3D = dataclasses.field(init=False, metadata={'dtype': Array3D.__args__[1].__args__[0]}) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
|
|
199
|
+
"""3D array encoding the connections between leaves in all dimensions."""
|
|
200
|
+
|
|
141
201
|
dimensionsTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
|
|
202
|
+
"""Total number of dimensions in the map shape."""
|
|
203
|
+
|
|
204
|
+
# 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
|
|
205
|
+
countDimensionsGapped: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
206
|
+
"""Tracks how many dimensions are gapped for each leaf."""
|
|
207
|
+
|
|
208
|
+
dimensionsUnconstrained: DatatypeLeavesTotal = dataclasses.field(default=None, init=True) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
209
|
+
"""Number of dimensions that are not constrained in the current folding state."""
|
|
210
|
+
|
|
211
|
+
gapRangeStart: Array1DElephino = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DElephino.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
212
|
+
"""Starting index for the gap range for each leaf."""
|
|
142
213
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
214
|
+
gapsWhere: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
215
|
+
"""Tracks where gaps occur in the folding pattern."""
|
|
216
|
+
|
|
217
|
+
leafAbove: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
218
|
+
"""For each leaf, stores the index of the leaf above it in the folding pattern."""
|
|
219
|
+
|
|
220
|
+
leafBelow: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
221
|
+
"""For each leaf, stores the index of the leaf below it in the folding pattern."""
|
|
222
|
+
|
|
223
|
+
foldGroups: Array1DFoldsTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DFoldsTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
|
|
224
|
+
"""Accumulator for fold groups across parallel tasks."""
|
|
150
225
|
|
|
151
226
|
foldsTotal: DatatypeFoldsTotal = DatatypeFoldsTotal(0)
|
|
227
|
+
"""The final computed total number of distinct folding patterns."""
|
|
228
|
+
|
|
152
229
|
gap1ndex: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
|
|
230
|
+
"""Current index into gaps array during algorithm execution."""
|
|
231
|
+
|
|
153
232
|
gap1ndexCeiling: DatatypeElephino = DatatypeElephino(0)
|
|
233
|
+
"""Upper limit for gap index during the current algorithm phase."""
|
|
234
|
+
|
|
154
235
|
groupsOfFolds: DatatypeFoldsTotal = dataclasses.field(default=DatatypeFoldsTotal(0), metadata={'theCountingIdentifier': True})
|
|
236
|
+
"""Accumulator for the number of fold groups found during computation."""
|
|
237
|
+
|
|
155
238
|
indexDimension: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
|
|
239
|
+
"""Current dimension being processed during algorithm execution."""
|
|
240
|
+
|
|
156
241
|
indexLeaf: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
|
|
242
|
+
"""Current leaf index during iteration."""
|
|
243
|
+
|
|
157
244
|
indexMiniGap: DatatypeElephino = DatatypeElephino(0)
|
|
245
|
+
"""Index used when filtering common gaps."""
|
|
246
|
+
|
|
158
247
|
leaf1ndex: DatatypeElephino = DatatypeElephino(1)
|
|
248
|
+
"""Active leaf being processed in the folding algorithm. Starts at 1, not 0."""
|
|
249
|
+
|
|
159
250
|
leafConnectee: DatatypeElephino = DatatypeElephino(0)
|
|
251
|
+
"""Leaf that is being connected to the active leaf."""
|
|
252
|
+
|
|
160
253
|
taskIndex: DatatypeLeavesTotal = DatatypeLeavesTotal(0)
|
|
254
|
+
"""Index of the current parallel task when using task divisions."""
|
|
161
255
|
|
|
162
256
|
def __post_init__(self) -> None:
|
|
163
257
|
from mapFolding.beDRY import getConnectionGraph, makeDataContainer
|
|
@@ -165,24 +259,22 @@ class ComputationState:
|
|
|
165
259
|
leavesTotalAsInt = int(self.leavesTotal)
|
|
166
260
|
self.connectionGraph = getConnectionGraph(self.mapShape, leavesTotalAsInt, self.__dataclass_fields__['connectionGraph'].metadata['dtype'])
|
|
167
261
|
|
|
168
|
-
if self.dimensionsUnconstrained is None: #
|
|
169
|
-
self.dimensionsUnconstrained = DatatypeLeavesTotal(int(self.dimensionsTotal))
|
|
262
|
+
if self.dimensionsUnconstrained is None: self.dimensionsUnconstrained = DatatypeLeavesTotal(int(self.dimensionsTotal)) # pyright: ignore[reportUnnecessaryComparison]
|
|
170
263
|
|
|
171
|
-
if self.foldGroups is None: #
|
|
264
|
+
if self.foldGroups is None: # pyright: ignore[reportUnnecessaryComparison]
|
|
172
265
|
self.foldGroups = makeDataContainer(max(2, int(self.taskDivisions) + 1), self.__dataclass_fields__['foldGroups'].metadata['dtype'])
|
|
173
266
|
self.foldGroups[-1] = self.leavesTotal
|
|
174
267
|
|
|
175
|
-
|
|
268
|
+
# Dataclasses, Default factories, and arguments in `ComputationState` https://github.com/hunterhogan/mapFolding/issues/12
|
|
269
|
+
if self.gapsWhere is None: self.gapsWhere = makeDataContainer(leavesTotalAsInt * leavesTotalAsInt + 1, self.__dataclass_fields__['gapsWhere'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
|
|
176
270
|
|
|
177
|
-
if self.countDimensionsGapped is None: self.countDimensionsGapped = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['countDimensionsGapped'].metadata['dtype']) #
|
|
178
|
-
if self.gapRangeStart is None: self.gapRangeStart = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['gapRangeStart'].metadata['dtype']) #
|
|
179
|
-
if self.leafAbove is None: self.leafAbove = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafAbove'].metadata['dtype']) #
|
|
180
|
-
if self.leafBelow is None: self.leafBelow = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafBelow'].metadata['dtype']) #
|
|
271
|
+
if self.countDimensionsGapped is None: self.countDimensionsGapped = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['countDimensionsGapped'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
|
|
272
|
+
if self.gapRangeStart is None: self.gapRangeStart = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['gapRangeStart'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
|
|
273
|
+
if self.leafAbove is None: self.leafAbove = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafAbove'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
|
|
274
|
+
if self.leafBelow is None: self.leafBelow = makeDataContainer(leavesTotalAsInt + 1, self.__dataclass_fields__['leafBelow'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison]
|
|
181
275
|
|
|
276
|
+
# Automatic, or not, calculation in dataclass `ComputationState` https://github.com/hunterhogan/mapFolding/issues/14
|
|
182
277
|
def getFoldsTotal(self) -> None:
|
|
183
278
|
self.foldsTotal = DatatypeFoldsTotal(self.foldGroups[0:-1].sum() * self.leavesTotal)
|
|
184
279
|
|
|
185
|
-
# =============================================================================
|
|
186
|
-
# The coping way.
|
|
187
|
-
|
|
188
280
|
class raiseIfNoneGitHubIssueNumber3(Exception): pass
|
mapFolding/toolboxFilesystem.py
CHANGED
|
@@ -22,7 +22,7 @@ The functions here adhere to a consistent approach to path handling:
|
|
|
22
22
|
- Progressive fallback strategies for saving critical computation results.
|
|
23
23
|
- Preemptive filesystem validation to detect issues before computation begins.
|
|
24
24
|
"""
|
|
25
|
-
from mapFolding
|
|
25
|
+
from mapFolding import The
|
|
26
26
|
from os import PathLike
|
|
27
27
|
from pathlib import Path, PurePath
|
|
28
28
|
from sys import modules as sysModules
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mapFolding
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Map folding algorithm with code transformation framework for optimizing numerical computations
|
|
5
|
+
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
|
+
License: CC-BY-NC-4.0
|
|
7
|
+
Project-URL: Donate, https://www.patreon.com/integrated
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunterhogan/mapFolding
|
|
9
|
+
Project-URL: Repository, https://github.com/hunterhogan/mapFolding.git
|
|
10
|
+
Project-URL: Issues, https://github.com/hunterhogan/mapFolding/issues
|
|
11
|
+
Keywords: A001415,A001416,A001417,A001418,A195646,algorithmic optimization,AST manipulation,code generation,code transformation,combinatorics,computational geometry,dataclass transformation,folding pattern enumeration,just-in-time compilation,map folding,Numba optimization,OEIS,performance optimization,source code analysis,stamp folding
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Natural Language :: English
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
27
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: autoflake
|
|
35
|
+
Requires-Dist: more_itertools
|
|
36
|
+
Requires-Dist: numba_progress
|
|
37
|
+
Requires-Dist: numba
|
|
38
|
+
Requires-Dist: numpy
|
|
39
|
+
Requires-Dist: platformdirs
|
|
40
|
+
Requires-Dist: python_minifier
|
|
41
|
+
Requires-Dist: tomli
|
|
42
|
+
Requires-Dist: Z0Z_tools
|
|
43
|
+
Provides-Extra: testing
|
|
44
|
+
Requires-Dist: mypy; extra == "testing"
|
|
45
|
+
Requires-Dist: pytest; extra == "testing"
|
|
46
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
47
|
+
Requires-Dist: pytest-env; extra == "testing"
|
|
48
|
+
Requires-Dist: pytest-xdist; extra == "testing"
|
|
49
|
+
Requires-Dist: pyupgrade; extra == "testing"
|
|
50
|
+
Requires-Dist: ruff; extra == "testing"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# mapFolding: High-Performance Algorithm Playground for Computational Enthusiasts 🗺️
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/mapFolding/)
|
|
56
|
+
[](https://github.com/hunterhogan/mapFolding/actions/workflows/pythonTests.yml)
|
|
57
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
58
|
+
|
|
59
|
+
**This package is for you if:**
|
|
60
|
+
|
|
61
|
+
- You're fascinated by computational algorithms and their optimization
|
|
62
|
+
- You want to explore AST transformation techniques for Python performance tuning
|
|
63
|
+
- You're interested in solving mathematical puzzles through code
|
|
64
|
+
- You're learning about Numba and advanced Python optimization
|
|
65
|
+
|
|
66
|
+
**This package is NOT for you if:**
|
|
67
|
+
|
|
68
|
+
- You're looking for a general-purpose folding simulation tool
|
|
69
|
+
- You need commercial-ready mapping software
|
|
70
|
+
- You want simple visualization of folding patterns
|
|
71
|
+
|
|
72
|
+
## What Does This Package Actually Do?
|
|
73
|
+
|
|
74
|
+
`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:
|
|
75
|
+
|
|
76
|
+
1. How to transform readable algorithms into blazingly fast implementations
|
|
77
|
+
2. Advanced techniques for Python optimization using AST manipulation
|
|
78
|
+
3. Numba acceleration with specialized compilation strategies
|
|
79
|
+
4. Algorithms for problems that grow combinatorially
|
|
80
|
+
|
|
81
|
+
The package has achieved new computational records, including first-ever calculations for large maps that were previously infeasible.
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# Compute the number of ways to fold a 5×5 grid:
|
|
85
|
+
from mapFolding import oeisIDfor_n
|
|
86
|
+
foldsTotal = oeisIDfor_n('A001418', 5) # Returns 186,086,600
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Key Benefits for Computational Enthusiasts
|
|
90
|
+
|
|
91
|
+
### 1. Algorithm Transformation Laboratory
|
|
92
|
+
|
|
93
|
+
See how the same algorithm evolves from readable Python to highly-optimized implementations:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# The intuitive, readable version:
|
|
97
|
+
def countFolds(mapShape):
|
|
98
|
+
# ...implement readable algorithm...
|
|
99
|
+
|
|
100
|
+
# The transformed, optimized version (auto-generated):
|
|
101
|
+
@numba.jit(nopython=True, parallel=True, fastmath=True)
|
|
102
|
+
def countFolds_optimized(shape_param):
|
|
103
|
+
# ...blazingly fast implementation...
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 2. Code Generation Framework
|
|
107
|
+
|
|
108
|
+
Study and extend a complete Python code transformation pipeline:
|
|
109
|
+
|
|
110
|
+
- AST analysis and manipulation
|
|
111
|
+
- Dataclass decomposition ("shattering")
|
|
112
|
+
- Automatic import management
|
|
113
|
+
- Type specialization for numerical computing
|
|
114
|
+
|
|
115
|
+
### 3. Exhaustive Test Framework
|
|
116
|
+
|
|
117
|
+
Leverage a sophisticated test suite for validating your own optimizations:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
# Test your own recipe implementation with just a few lines:
|
|
121
|
+
@pytest.fixture
|
|
122
|
+
def myCustomRecipeFixture(useThisDispatcher, pathTmpTesting):
|
|
123
|
+
myRecipe = RecipeSynthesizeFlow(
|
|
124
|
+
# Your custom configuration here
|
|
125
|
+
)
|
|
126
|
+
# ...transformation code...
|
|
127
|
+
return customDispatcher
|
|
128
|
+
|
|
129
|
+
def test_myCustomImplementation(myCustomRecipeFixture):
|
|
130
|
+
# Automatic validation against known values
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Installation and Getting Started
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
pip install mapFolding
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Try a quick calculation:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from mapFolding import oeisIDfor_n
|
|
143
|
+
|
|
144
|
+
# Calculate ways to fold a 2×4 map
|
|
145
|
+
result = oeisIDfor_n('A001415', 4) # Returns 8
|
|
146
|
+
print(f"A 2×4 map can be folded {result} different ways")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Mathematical Background (For the Curious)
|
|
150
|
+
|
|
151
|
+
The map folding problem was introduced by Lunnon in 1971 and connects to combinatorial geometry, computational complexity, and integer sequence analysis. The calculations provide entries to the Online Encyclopedia of Integer Sequences (OEIS).
|
|
152
|
+
|
|
153
|
+
This package implements several OEIS sequences, including:
|
|
154
|
+
|
|
155
|
+
- A001415: Number of ways to fold a 2×n strip (now calculated up to n=20!)
|
|
156
|
+
- A001418: Number of ways to fold an n×n square grid
|
|
157
|
+
|
|
158
|
+
## Explore the Repository
|
|
159
|
+
|
|
160
|
+
The repository structure reveals the package's educational value:
|
|
161
|
+
|
|
162
|
+
- `reference/`: Historical implementations and algorithm evolution
|
|
163
|
+
- `someAssemblyRequired/`: Code transformation framework
|
|
164
|
+
- `tests/`: Comprehensive test suite with fixtures for your own implementations
|
|
165
|
+
|
|
166
|
+
## Who Is This For, Really?
|
|
167
|
+
|
|
168
|
+
If you've read this far and are intrigued by computational puzzles, algorithm optimization, or Python performance techniques, this package offers a playground for exploration. It's particularly valuable for:
|
|
169
|
+
|
|
170
|
+
- Computer science students studying algorithm optimization
|
|
171
|
+
- Python developers exploring Numba and AST manipulation
|
|
172
|
+
- Computational mathematicians interested in combinatorial problems
|
|
173
|
+
- Anyone fascinated by the intersection of mathematics and computing
|
|
174
|
+
|
|
175
|
+
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.
|
|
176
|
+
|
|
177
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=TXCWanI9snueVZMTtjLNpvG4um7vyPXmJeIvvrBuJCA,3887
|
|
2
|
+
mapFolding/basecamp.py,sha256=L0eduDE8-5SnEYpnI-GHs-am4ico9kEn75SuOUgibZ8,4770
|
|
3
|
+
mapFolding/beDRY.py,sha256=cWMHETsTofpGMKmbEB3E8NiiujmyYOVq0HW-VqI3-Pk,15272
|
|
4
|
+
mapFolding/oeis.py,sha256=qgmlMbqDZ1O3DKrhKttm2J0Lwm9iGGKqLhwJQNULLGM,16995
|
|
5
|
+
mapFolding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mapFolding/theDao.py,sha256=MVopt1LzhdIQYA97SEoq9bdzct6hbK0lEyPxBAAlVTc,9934
|
|
7
|
+
mapFolding/theSSOT.py,sha256=20Z0Cs2Sj5EZPFXE6dT_rb6iH6DTBVq-gaysemQTVPc,16829
|
|
8
|
+
mapFolding/toolboxFilesystem.py,sha256=5RZ9CshZwgjejH_sMG4_Kk4JX3QJLkK30OqFeL5jFWg,9974
|
|
9
|
+
mapFolding/reference/__init__.py,sha256=UIEU8BJR_YDzjFQcLel3XtHzOCJiOUGlGiWzOzbvhik,2206
|
|
10
|
+
mapFolding/reference/flattened.py,sha256=QK1xG9SllqCoi68e86Hyl9d9ATUAAFNpTQI-3zmcp5I,16072
|
|
11
|
+
mapFolding/reference/hunterNumba.py,sha256=iLfyqwGdAh6c5GbapnKsWhAsNsR3O-fyGGHAdohluLw,7258
|
|
12
|
+
mapFolding/reference/irvineJavaPort.py,sha256=UEfIX4QbPLl5jnyfYIyX5YRR3_rYvPUikK8jLehsFko,4076
|
|
13
|
+
mapFolding/reference/jaxCount.py,sha256=TuDNKOnyhQfuixKmIxO9Algv7dvy7KMGhgsV3h96FGE,14853
|
|
14
|
+
mapFolding/reference/lunnanNumpy.py,sha256=mMgrgbrBpe4nmo72ThEI-MGH0OwEHmfMPczSXHp2qKo,4357
|
|
15
|
+
mapFolding/reference/lunnanWhile.py,sha256=ZL8GAQtPs5nJZSgoDl5USrLSS_zs03y98y1Z9E4jOmQ,3799
|
|
16
|
+
mapFolding/reference/rotatedEntryPoint.py,sha256=5ughpKUT2JQhoAKgoDUdYNjgWQYPGV8v-7dWEAdDmfE,10274
|
|
17
|
+
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=yJZAVLVdoXqHag2_N6_6CT-Q6HXBgRro-eny93-Rlpw,9307
|
|
18
|
+
mapFolding/reference/jobsCompleted/__init__.py,sha256=TU93ZGUW1xEkT6d9mQFn_rp5DvRy0ZslEB2Q6MF5ZDc,2596
|
|
19
|
+
mapFolding/reference/jobsCompleted/[2x19]/p2x19.py,sha256=_tvYtfzMWVo2VtUbIAieoscb4N8FFflgTdW4-ljBUuA,19626
|
|
20
|
+
mapFolding/reference/jobsCompleted/p2x19/p2x19.py,sha256=eZEw4Me4ocTt6VXoK2-Sbd5SowZtxRIbN9dZmc7OCVg,6395
|
|
21
|
+
mapFolding/someAssemblyRequired/__init__.py,sha256=ZtYMGjxMnl1mnwSLSBm73meaXP1P_MBd4RlAwlOyRXY,3318
|
|
22
|
+
mapFolding/someAssemblyRequired/_theTypes.py,sha256=YFiQI6zsrFNruvqGDXHJVH0OWXsOj9EwDrt4G59OVHA,3995
|
|
23
|
+
mapFolding/someAssemblyRequired/_tool_Make.py,sha256=-ISVrr09mYuo5dhJfjYs38jhSd_IjYPrheGC6RWUmBI,7373
|
|
24
|
+
mapFolding/someAssemblyRequired/_tool_Then.py,sha256=uc1of9ZPCp5Vrh5Qg80giatGHwT9wRzW3dWaKxGYQsg,4125
|
|
25
|
+
mapFolding/someAssemblyRequired/_toolboxAntecedents.py,sha256=bK5IDfHZ_p1p4rhQ1Q21FM4J8i9Yl6gD7mAOw6NJahU,10591
|
|
26
|
+
mapFolding/someAssemblyRequired/_toolboxContainers.py,sha256=yIad3WTL60qGdLd87Wr-FJccNyhGLD3tsFJGA0sdknU,24183
|
|
27
|
+
mapFolding/someAssemblyRequired/_toolboxPython.py,sha256=dkEWp4-SAc8xe-pkeCXh6iVUSolSlXSFCBun1Asn1gE,7639
|
|
28
|
+
mapFolding/someAssemblyRequired/getLLVMforNoReason.py,sha256=9RPU6vK_eUg64GtVFI_nZnvUryXw8gfHJs9NyDYHIvg,2745
|
|
29
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaJob.py,sha256=7jwUcHpZk3T_PoFgLtZhYayx5VbJP1f15a2JPlZegFI,14453
|
|
30
|
+
mapFolding/someAssemblyRequired/toolboxNumba.py,sha256=GkBmcztmGSwm2bolK76FPPdc2c9AWrtMGztgZb3rS7Y,24381
|
|
31
|
+
mapFolding/someAssemblyRequired/transformationTools.py,sha256=bI8ApAb-Z-VIdRa-ORQilnT6LjejTPeTyC5nOrpDz94,23323
|
|
32
|
+
mapFolding/syntheticModules/__init__.py,sha256=evVFqhCGa-WZKDiLcnQWjs-Bj34eRnfSLqz_d7dFYZY,83
|
|
33
|
+
mapFolding/syntheticModules/numbaCount_doTheNeedful.py,sha256=vSqV1WbyZ0mXg2TNgMR9EndiitVKM8GQa72ijOUCEZ8,15659
|
|
34
|
+
mapfolding-0.9.0.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
35
|
+
tests/__init__.py,sha256=UIvSWWz_anRXBELKPOdhfRoZ4hArHQTvghHrRquDHHw,1940
|
|
36
|
+
tests/conftest.py,sha256=G8vhDSTdTbYZUFBUKLFOUOzDL1Ja6NVZmICCh4biZas,14298
|
|
37
|
+
tests/test_computations.py,sha256=oT2f_7Hzis3dLgOmfzlA_I-5fG3D0NURWab0rZkARfc,6152
|
|
38
|
+
tests/test_filesystem.py,sha256=T2DkjBoI3lW6tCxd5BilPmUFrVukNKLjOOZVZxLM560,3004
|
|
39
|
+
tests/test_oeis.py,sha256=uxvwmgbnylSDdsVJfuAT0LuYLbIVFwSgdLxHm-xUGBM,5043
|
|
40
|
+
tests/test_other.py,sha256=zZmSZpcNsk4oeD4EHNLMNtmiz9hnwwoV62DFSQrLKwo,4258
|
|
41
|
+
tests/test_tasks.py,sha256=yrExYvFP23TEA3ta0IotMNmi59rwQ3Y9hA3fwvIhxTE,2851
|
|
42
|
+
mapfolding-0.9.0.dist-info/METADATA,sha256=yjarli2blxeiwu9i1nFOrPT6gevc6YRzIbDy8KMbbr4,7445
|
|
43
|
+
mapfolding-0.9.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
44
|
+
mapfolding-0.9.0.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
45
|
+
mapfolding-0.9.0.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
46
|
+
mapfolding-0.9.0.dist-info/RECORD,,
|
tests/__init__.py
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Map Folding Test Suite and Validation Framework
|
|
3
|
+
|
|
4
|
+
This test suite provides comprehensive testing capabilities for the mapFolding package
|
|
5
|
+
and its optimization framework. It is specifically designed to enable both package
|
|
6
|
+
maintenance and custom extension testing, making it easy for users to validate their
|
|
7
|
+
own recipe configurations and job implementations.
|
|
8
|
+
|
|
9
|
+
## Key Testing Capabilities
|
|
10
|
+
|
|
11
|
+
1. **Algorithm Validation**
|
|
12
|
+
- Tests core algorithm correctness against known OEIS sequence values
|
|
13
|
+
- Validates both sequential and parallel execution paths
|
|
14
|
+
- Ensures consistency across different implementation strategies
|
|
15
|
+
|
|
16
|
+
2. **Code Generation Testing**
|
|
17
|
+
- Tests the AST transformation pipeline from source to optimized implementations
|
|
18
|
+
- Validates that generated Numba-accelerated modules produce correct results
|
|
19
|
+
- Ensures robust code generation across different parameter sets
|
|
20
|
+
|
|
21
|
+
3. **Job-Specific Testing**
|
|
22
|
+
- Tests specialized job module generation for specific map shapes
|
|
23
|
+
- Validates execution of the generated modules
|
|
24
|
+
- Verifies correct output file creation and value storage
|
|
25
|
+
|
|
26
|
+
## Testing Your Own Implementations
|
|
27
|
+
|
|
28
|
+
This suite is designed to make it easy to test your custom recipes and jobs:
|
|
29
|
+
|
|
30
|
+
### For Custom Recipes (RecipeSynthesizeFlow):
|
|
31
|
+
Copy and adapt the `syntheticDispatcherFixture` and associated tests from
|
|
32
|
+
`test_computations.py` to validate your customized code transformation pipelines.
|
|
33
|
+
|
|
34
|
+
### For Custom Jobs (RecipeJob):
|
|
35
|
+
Copy and adapt the `test_writeJobNumba` function to test specialized job modules
|
|
36
|
+
for specific map shapes with your custom configurations.
|
|
37
|
+
|
|
38
|
+
The entire test infrastructure is built on fixtures and utilities that handle
|
|
39
|
+
complex setup and validation, allowing you to focus on your implementation details
|
|
40
|
+
while leveraging the existing validation framework.
|
|
41
|
+
|
|
42
|
+
See the module docstrings in `test_computations.py` and `conftest.py` for detailed
|
|
43
|
+
guidance on adapting these tests for your own purposes.
|
|
44
|
+
"""
|