mapFolding 0.8.1__py3-none-any.whl → 0.8.2__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/basecamp.py +2 -2
- mapFolding/beDRY.py +24 -31
- mapFolding/oeis.py +2 -2
- mapFolding/reference/__init__.py +38 -0
- mapFolding/reference/flattened.py +20 -2
- mapFolding/reference/hunterNumba.py +24 -0
- mapFolding/reference/irvineJavaPort.py +12 -0
- mapFolding/reference/{jax.py → jaxCount.py} +46 -27
- mapFolding/reference/lunnanNumpy.py +16 -1
- mapFolding/reference/lunnanWhile.py +15 -1
- mapFolding/reference/rotatedEntryPoint.py +18 -0
- mapFolding/reference/total_countPlus1vsPlusN.py +226 -203
- mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +5 -8
- mapFolding/someAssemblyRequired/transformDataStructures.py +10 -4
- mapFolding/someAssemblyRequired/transformationTools.py +19 -28
- mapFolding/theSSOT.py +70 -121
- {mapfolding-0.8.1.dist-info → mapfolding-0.8.2.dist-info}/METADATA +54 -30
- mapfolding-0.8.2.dist-info/RECORD +39 -0
- {mapfolding-0.8.1.dist-info → mapfolding-0.8.2.dist-info}/WHEEL +1 -1
- tests/conftest.py +43 -33
- tests/test_computations.py +7 -7
- tests/test_other.py +2 -2
- mapfolding-0.8.1.dist-info/RECORD +0 -39
- {mapfolding-0.8.1.dist-info → mapfolding-0.8.2.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.8.1.dist-info → mapfolding-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {mapfolding-0.8.1.dist-info → mapfolding-0.8.2.dist-info}/top_level.txt +0 -0
mapFolding/theSSOT.py
CHANGED
|
@@ -19,114 +19,85 @@ to avoid namespace collisions when transforming algorithms.
|
|
|
19
19
|
from collections.abc import Callable
|
|
20
20
|
from importlib import import_module as importlib_import_module
|
|
21
21
|
from inspect import getfile as inspect_getfile
|
|
22
|
-
from numpy import dtype, int64 as numpy_int64, int16 as numpy_int16, ndarray
|
|
22
|
+
from numpy import dtype, int64 as numpy_int64, int16 as numpy_int16, ndarray
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
from sys import modules as sysModules
|
|
25
25
|
from tomli import load as tomli_load
|
|
26
26
|
from types import ModuleType
|
|
27
|
-
from typing import
|
|
27
|
+
from typing import TypeAlias
|
|
28
28
|
import dataclasses
|
|
29
29
|
|
|
30
|
-
"""
|
|
31
|
-
2025 March 11
|
|
32
|
-
Note to self: fundamental concept in Python:
|
|
33
|
-
Identifiers: scope and resolution, LEGB (Local, Enclosing, Global, Builtin)
|
|
34
|
-
- Local: Inside the function
|
|
35
|
-
- Enclosing: Inside enclosing functions
|
|
36
|
-
- Global: At the uppermost level
|
|
37
|
-
- Builtin: Python's built-in names
|
|
38
|
-
"""
|
|
39
|
-
|
|
40
|
-
# I _think_, in theSSOT, I have abstracted the flow settings to only these couple of lines:
|
|
41
30
|
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
31
|
+
# I _think_, in theSSOT, I have abstracted the flow settings to only these couple of lines:
|
|
42
32
|
packageFlowSynthetic = 'numba'
|
|
43
33
|
# Z0Z_packageFlow = 'algorithm'
|
|
44
34
|
Z0Z_packageFlow = packageFlowSynthetic
|
|
45
35
|
Z0Z_concurrencyPackage = 'multiprocessing'
|
|
46
|
-
# =============================================================================
|
|
47
|
-
# The Wrong Way The Wrong Way The Wrong Way The Wrong Way The Wrong Way
|
|
48
|
-
# Evaluate When Packaging Evaluate When Packaging Evaluate When Packaging
|
|
49
|
-
|
|
50
|
-
sourceAlgorithmPACKAGING: str = 'theDao'
|
|
51
|
-
datatypePackagePACKAGING: Final[str] = 'numpy'
|
|
52
|
-
dispatcherCallablePACKAGING: str = 'doTheNeedful'
|
|
53
|
-
moduleOfSyntheticModulesPACKAGING: Final[str] = 'syntheticModules'
|
|
54
36
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
dataclassInstancePACKAGING: str = 'state'
|
|
58
|
-
dataclassInstanceTaskDistributionPACKAGING = dataclassInstancePACKAGING + 'Parallel'
|
|
59
|
-
|
|
60
|
-
sourceInitializeCallablePACKAGING = 'countInitialize'
|
|
61
|
-
sourceSequentialCallablePACKAGING = 'countSequential'
|
|
62
|
-
sourceParallelCallablePACKAGING = 'countParallel'
|
|
37
|
+
# =============================================================================
|
|
38
|
+
# The Wrong Way: Evaluate When Packaging
|
|
63
39
|
|
|
64
40
|
try:
|
|
65
|
-
|
|
41
|
+
packageNamePACKAGING: str = tomli_load(Path("../pyproject.toml").open('rb'))["project"]["name"]
|
|
66
42
|
except Exception:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# =============================================================================
|
|
70
|
-
# The Wrong Way The Wrong Way The Wrong Way The Wrong Way The Wrong Way
|
|
71
|
-
# Evaluate When Installing Evaluate When Installing Evaluate When Installing
|
|
43
|
+
packageNamePACKAGING = "mapFolding"
|
|
72
44
|
|
|
73
|
-
|
|
45
|
+
# The Wrong Way: Evaluate When Installing
|
|
74
46
|
|
|
75
47
|
def getPathPackageINSTALLING() -> Path:
|
|
76
|
-
pathPackage: Path = Path(inspect_getfile(importlib_import_module(
|
|
48
|
+
pathPackage: Path = Path(inspect_getfile(importlib_import_module(packageNamePACKAGING)))
|
|
77
49
|
if pathPackage.is_file():
|
|
78
50
|
pathPackage = pathPackage.parent
|
|
79
51
|
return pathPackage
|
|
80
52
|
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
53
|
+
# The following is an improvement, but it is not the full solution.
|
|
54
|
+
# I hope that the standardized markers, `metadata={'evaluateWhen': 'packaging'}` will help to automate
|
|
55
|
+
# whatever needs to happen so that the following is well implemented.
|
|
56
|
+
@dataclasses.dataclass(frozen=True)
|
|
57
|
+
class PackageSettings:
|
|
58
|
+
concurrencyPackage = Z0Z_concurrencyPackage
|
|
59
|
+
dataclassIdentifier: str = dataclasses.field(default='ComputationState', metadata={'evaluateWhen': 'packaging'})
|
|
60
|
+
dataclassInstance: str = dataclasses.field(default='state', metadata={'evaluateWhen': 'packaging'})
|
|
61
|
+
dataclassInstanceTaskDistributionSuffix: str = dataclasses.field(default='Parallel', metadata={'evaluateWhen': 'packaging'})
|
|
62
|
+
dataclassModule: str = dataclasses.field(default='theSSOT', metadata={'evaluateWhen': 'packaging'})
|
|
63
|
+
datatypePackage: str = dataclasses.field(default='numpy', metadata={'evaluateWhen': 'packaging'})
|
|
64
|
+
dispatcherCallable: str = dataclasses.field(default='doTheNeedful', metadata={'evaluateWhen': 'packaging'})
|
|
65
|
+
fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
|
|
66
|
+
moduleOfSyntheticModules: str = dataclasses.field(default='syntheticModules', metadata={'evaluateWhen': 'packaging'})
|
|
67
|
+
packageName: str = dataclasses.field(default = packageNamePACKAGING, metadata={'evaluateWhen': 'packaging'})
|
|
68
|
+
pathPackage: Path = dataclasses.field(default_factory=getPathPackageINSTALLING, init=False, metadata={'evaluateWhen': 'installing'})
|
|
69
|
+
sourceAlgorithm: str = dataclasses.field(default='theDao', metadata={'evaluateWhen': 'packaging'})
|
|
70
|
+
sourceConcurrencyManagerIdentifier: str = dataclasses.field(default='submit', metadata={'evaluateWhen': 'packaging'})
|
|
71
|
+
sourceConcurrencyManagerNamespace: str = dataclasses.field(default='concurrencyManager', metadata={'evaluateWhen': 'packaging'})
|
|
72
|
+
sourceInitializeCallable: str = dataclasses.field(default='countInitialize', metadata={'evaluateWhen': 'packaging'})
|
|
73
|
+
sourceParallelCallable: str = dataclasses.field(default='countParallel', metadata={'evaluateWhen': 'packaging'})
|
|
74
|
+
sourceSequentialCallable: str = dataclasses.field(default='countSequential', metadata={'evaluateWhen': 'packaging'})
|
|
75
|
+
|
|
76
|
+
@property # These are not fields, and that annoys me.
|
|
77
|
+
def dataclassInstanceTaskDistribution(self) -> str:
|
|
78
|
+
""" Compute the task distribution identifier by concatenating dataclassInstance and dataclassInstanceTaskDistributionSuffix. """
|
|
79
|
+
# it follows that `metadata={'evaluateWhen': 'packaging'}`
|
|
80
|
+
return self.dataclassInstance + self.dataclassInstanceTaskDistributionSuffix
|
|
81
|
+
|
|
82
|
+
@property # These are not fields, and that annoys me.
|
|
83
|
+
def logicalPathModuleSourceAlgorithm(self) -> str:
|
|
84
|
+
""" Compute the logical path module for the source algorithm by joining packageName and sourceAlgorithm. """
|
|
85
|
+
# it follows that `metadata={'evaluateWhen': 'packaging'}`
|
|
86
|
+
return '.'.join([self.packageName, self.sourceAlgorithm])
|
|
87
|
+
|
|
88
|
+
@property # These are not fields, and that annoys me.
|
|
89
|
+
def logicalPathModuleDataclass(self) -> str:
|
|
90
|
+
""" Compute the logical path module for the dataclass by joining packageName and dataclassModule. """
|
|
91
|
+
# it follows that `metadata={'evaluateWhen': 'packaging'}`
|
|
92
|
+
return '.'.join([self.packageName, self.dataclassModule])
|
|
93
|
+
|
|
94
|
+
The = PackageSettings()
|
|
113
95
|
|
|
114
96
|
# =============================================================================
|
|
115
|
-
|
|
116
|
-
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
117
|
-
concurrencyPackage: str = Z0Z_packageFlow
|
|
118
|
-
concurrencyPackage = Z0Z_concurrencyPackage
|
|
119
|
-
|
|
120
|
-
# =============================================================================
|
|
121
|
-
# The relatively flexible type system needs a different paradigm, but I don't
|
|
122
|
-
# know what it should be. The system needs to 1) help optimize computation, 2)
|
|
123
|
-
# make it possible to change the basic type of the package (e.g., from numpy
|
|
124
|
-
# to superTypePy), 3) make it possible to synthesize the optimized flow of used
|
|
125
|
-
# by the package, and 4) make it possible to synthesize arbitrary modules with
|
|
126
|
-
# different type systems.
|
|
97
|
+
# Flexible Data Structure System Needs Enhanced Paradigm https://github.com/hunterhogan/mapFolding/issues/9
|
|
127
98
|
|
|
128
99
|
DatatypeLeavesTotal: TypeAlias = int
|
|
129
|
-
# this would be uint8, but mapShape (2,2,2,2, 2,2,2,2) has 256 leaves, so generic containers accommodate
|
|
100
|
+
# 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
|
|
130
101
|
numpyLeavesTotal: TypeAlias = numpy_int16
|
|
131
102
|
|
|
132
103
|
DatatypeElephino: TypeAlias = int
|
|
@@ -134,18 +105,12 @@ numpyElephino: TypeAlias = numpy_int16
|
|
|
134
105
|
|
|
135
106
|
DatatypeFoldsTotal: TypeAlias = int
|
|
136
107
|
numpyFoldsTotal: TypeAlias = numpy_int64
|
|
137
|
-
numpyDtypeDefault = numpyFoldsTotal
|
|
138
108
|
|
|
139
109
|
Array3D: TypeAlias = ndarray[tuple[int, int, int], dtype[numpyLeavesTotal]]
|
|
140
110
|
Array1DLeavesTotal: TypeAlias = ndarray[tuple[int], dtype[numpyLeavesTotal]]
|
|
141
111
|
Array1DElephino: TypeAlias = ndarray[tuple[int], dtype[numpyElephino]]
|
|
142
112
|
Array1DFoldsTotal: TypeAlias = ndarray[tuple[int], dtype[numpyFoldsTotal]]
|
|
143
113
|
|
|
144
|
-
# =============================================================================
|
|
145
|
-
# The right way.
|
|
146
|
-
# (The dataclass, not the typing of the dataclass.)
|
|
147
|
-
# (Also, my noobplementation of the dataclass certainly needs improvement.)
|
|
148
|
-
|
|
149
114
|
@dataclasses.dataclass
|
|
150
115
|
class ComputationState:
|
|
151
116
|
mapShape: tuple[DatatypeLeavesTotal, ...]
|
|
@@ -191,9 +156,9 @@ class ComputationState:
|
|
|
191
156
|
leavesTotalAsInt = int(self.leavesTotal)
|
|
192
157
|
|
|
193
158
|
if self.countDimensionsGapped is None:
|
|
194
|
-
self.countDimensionsGapped = makeDataContainer(leavesTotalAsInt + 1,
|
|
159
|
+
self.countDimensionsGapped = makeDataContainer(leavesTotalAsInt + 1, numpyLeavesTotal)
|
|
195
160
|
if self.gapRangeStart is None:
|
|
196
|
-
self.gapRangeStart = makeDataContainer(leavesTotalAsInt + 1,
|
|
161
|
+
self.gapRangeStart = makeDataContainer(leavesTotalAsInt + 1, numpyElephino)
|
|
197
162
|
if self.gapsWhere is None:
|
|
198
163
|
self.gapsWhere = makeDataContainer(leavesTotalAsInt * leavesTotalAsInt + 1, numpyLeavesTotal)
|
|
199
164
|
if self.leafAbove is None:
|
|
@@ -205,75 +170,50 @@ class ComputationState:
|
|
|
205
170
|
self.foldsTotal = DatatypeFoldsTotal(self.foldGroups[0:-1].sum() * self.leavesTotal)
|
|
206
171
|
|
|
207
172
|
# =============================================================================
|
|
208
|
-
# The most right way I know how to implement.
|
|
209
|
-
|
|
210
|
-
theLogicalPathModuleSourceAlgorithm: str = '.'.join([thePackageName, theSourceAlgorithm])
|
|
211
|
-
theLogicalPathModuleDispatcher: str = theLogicalPathModuleSourceAlgorithm
|
|
212
|
-
theLogicalPathModuleDataclass: str = '.'.join([thePackageName, theDataclassModule])
|
|
213
|
-
|
|
214
|
-
def getSourceAlgorithm() -> ModuleType:
|
|
215
|
-
moduleImported: ModuleType = importlib_import_module(theLogicalPathModuleSourceAlgorithm)
|
|
216
|
-
return moduleImported
|
|
217
|
-
|
|
218
|
-
def getAlgorithmDispatcher() -> Callable[[ComputationState], ComputationState]:
|
|
219
|
-
moduleImported: ModuleType = getSourceAlgorithm()
|
|
220
|
-
dispatcherCallable = getattr(moduleImported, theDispatcherCallable)
|
|
221
|
-
return dispatcherCallable
|
|
222
|
-
|
|
223
|
-
def getPathSyntheticModules() -> Path:
|
|
224
|
-
return thePathPackage / theModuleOfSyntheticModules
|
|
225
173
|
|
|
226
174
|
# TODO learn how to see this from the user's perspective
|
|
227
175
|
def getPathJobRootDEFAULT() -> Path:
|
|
228
176
|
if 'google.colab' in sysModules:
|
|
229
177
|
pathJobDEFAULT: Path = Path("/content/drive/MyDrive") / "jobs"
|
|
230
178
|
else:
|
|
231
|
-
pathJobDEFAULT =
|
|
179
|
+
pathJobDEFAULT = The.pathPackage / "jobs"
|
|
232
180
|
return pathJobDEFAULT
|
|
233
181
|
|
|
234
182
|
_datatypePackage: str = ''
|
|
235
183
|
def getDatatypePackage() -> str:
|
|
236
184
|
global _datatypePackage
|
|
237
185
|
if not _datatypePackage:
|
|
238
|
-
_datatypePackage =
|
|
186
|
+
_datatypePackage = The.datatypePackage
|
|
239
187
|
return _datatypePackage
|
|
240
188
|
|
|
241
|
-
def getNumpyDtypeDefault() -> type[signedinteger[Any]]:
|
|
242
|
-
return numpyDtypeDefault
|
|
243
|
-
|
|
244
189
|
# =============================================================================
|
|
245
190
|
# The coping way.
|
|
246
191
|
|
|
247
192
|
class raiseIfNoneGitHubIssueNumber3(Exception): pass
|
|
248
193
|
|
|
249
194
|
# =============================================================================
|
|
250
|
-
# Temporary or transient or something; probably still the wrong way
|
|
251
|
-
|
|
252
195
|
# THIS IS A STUPID SYSTEM BUT I CAN'T FIGURE OUT AN IMPROVEMENT
|
|
253
196
|
# NOTE This section for _default_ values probably has value
|
|
254
197
|
# https://github.com/hunterhogan/mapFolding/issues/4
|
|
255
198
|
theFormatStrModuleSynthetic = "{packageFlow}Count"
|
|
256
199
|
theFormatStrModuleForCallableSynthetic = theFormatStrModuleSynthetic + "_{callableTarget}"
|
|
257
200
|
|
|
258
|
-
|
|
259
|
-
theLogicalPathModuleDispatcherSynthetic: str = '.'.join([thePackageName, theModuleOfSyntheticModules, theModuleDispatcherSynthetic])
|
|
201
|
+
theLogicalPathModuleDispatcher: str = The.logicalPathModuleSourceAlgorithm
|
|
260
202
|
|
|
261
|
-
|
|
262
|
-
|
|
203
|
+
theModuleDispatcherSynthetic: str = theFormatStrModuleForCallableSynthetic.format(packageFlow=packageFlowSynthetic, callableTarget=The.dispatcherCallable)
|
|
204
|
+
theLogicalPathModuleDispatcherSynthetic: str = '.'.join([The.packageName, The.moduleOfSyntheticModules, theModuleDispatcherSynthetic])
|
|
263
205
|
|
|
264
|
-
# https://github.com/hunterhogan/mapFolding/issues/4
|
|
265
206
|
if Z0Z_packageFlow == packageFlowSynthetic: # pyright: ignore [reportUnnecessaryComparison]
|
|
266
207
|
# NOTE this as a default value _might_ have value
|
|
267
208
|
theLogicalPathModuleDispatcher = theLogicalPathModuleDispatcherSynthetic
|
|
268
209
|
|
|
269
|
-
# https://github.com/hunterhogan/mapFolding/issues/4
|
|
270
210
|
# dynamically set the return type https://github.com/hunterhogan/mapFolding/issues/5
|
|
271
211
|
def getPackageDispatcher() -> Callable[[ComputationState], ComputationState]:
|
|
272
212
|
# NOTE but this part, if the package flow is synthetic, probably needs to be delegated
|
|
273
213
|
# to the authority for creating _that_ synthetic flow.
|
|
274
214
|
|
|
275
215
|
moduleImported: ModuleType = importlib_import_module(theLogicalPathModuleDispatcher)
|
|
276
|
-
dispatcherCallable = getattr(moduleImported,
|
|
216
|
+
dispatcherCallable = getattr(moduleImported, The.dispatcherCallable)
|
|
277
217
|
return dispatcherCallable
|
|
278
218
|
|
|
279
219
|
"""Technical concepts I am likely using and likely want to use more effectively:
|
|
@@ -288,4 +228,13 @@ theSSOT and yourSSOT
|
|
|
288
228
|
----
|
|
289
229
|
delay realization/instantiation until a concrete value is desired
|
|
290
230
|
moment of truth: when the value is needed, not when the value is defined
|
|
231
|
+
|
|
232
|
+
----
|
|
233
|
+
2025 March 11
|
|
234
|
+
Note to self: fundamental concept in Python:
|
|
235
|
+
Identifiers: scope and resolution, LEGB (Local, Enclosing, Global, Builtin)
|
|
236
|
+
- Local: Inside the function
|
|
237
|
+
- Enclosing: Inside enclosing functions
|
|
238
|
+
- Global: At the uppermost level
|
|
239
|
+
- Builtin: Python's built-in names
|
|
291
240
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapFolding
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
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
|
|
@@ -99,51 +99,75 @@ Available OEIS sequences:
|
|
|
99
99
|
A195646: Number of ways of folding a 3 X 3 X ... X 3 n-dimensional map.
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
### 2. **Algorithm Zoo** 🦒
|
|
102
|
+
### 2. **Algorithm Zoo: A Historical and Performance Journey** 🦒
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
- The /reference directory.
|
|
106
|
-
- **Numba-JIT Accelerated**: Up to 1000× faster than pure Python ([benchmarks](https://github.com/hunterhogan/mapFolding/blob/mapFolding/notes/Speed%20highlights.md))
|
|
104
|
+
This package offers a comprehensive collection of map folding algorithm implementations that showcase its evolution from historical origins to high-performance computation:
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
- **Historical Implementations**:
|
|
107
|
+
- Carefully restored versions of Lunnon's 1971 original [algorithm](https://github.com/hunterhogan/mapFolding/blob/mapFolding/reference/foldings.txt) with corrections
|
|
108
|
+
- Atlas Autocode reconstruction in the `reference/foldings.AA` file
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
- **Direct Translations**:
|
|
111
|
+
- Python translations following the original control flow (`lunnanWhile.py`)
|
|
112
|
+
- NumPy-based vectorized implementations (`lunnanNumpy.py`)
|
|
111
113
|
|
|
112
|
-
- **
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- Just-in-time compilation with Numba and various optimization profiles
|
|
117
|
-
- LLVM IR extraction for low-level algorithmic analysis
|
|
114
|
+
- **Modern Implementations**:
|
|
115
|
+
- Java port adaptations (`irvineJavaPort.py`) providing cleaner procedural implementations
|
|
116
|
+
- Experimental JAX version (`jaxCount.py`) exploring GPU acceleration potential
|
|
117
|
+
- Semantically decomposed version (`flattened.py`) with clear function boundaries
|
|
118
118
|
|
|
119
|
-
- **
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
- Development of specialized algorithms for specific map dimensions
|
|
119
|
+
- **Performance Optimized**:
|
|
120
|
+
- Numba-JIT accelerated implementations up to 1000× faster than pure Python (see [benchmarks](https://github.com/hunterhogan/mapFolding/blob/mapFolding/notes/Speed%20highlights.md))
|
|
121
|
+
- Algorithmic optimizations showcasing subtle yet powerful performance differences (`total_countPlus1vsPlusN.py`)
|
|
123
122
|
|
|
124
|
-
|
|
123
|
+
The `reference` directory serves as both a historical archive and an educational resource for understanding algorithm evolution.
|
|
125
124
|
|
|
126
|
-
|
|
125
|
+
### 3. **Algorithmic Transformation: From Readability to Speed** 🔬
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
The package provides a sophisticated transformation framework that bridges the gap between human-readable algorithms and high-performance computation:
|
|
128
|
+
|
|
129
|
+
- **Core Algorithm Understanding**:
|
|
130
|
+
- Study the functional state-transformation approach in `theDao.py` with clear, isolated functions
|
|
131
|
+
- Explore the semantic decomposition in `reference/flattened.py` to understand algorithm sections
|
|
132
|
+
|
|
133
|
+
- **Code Transformation Pipeline**:
|
|
134
|
+
- **AST Manipulation**: Analyzes and transforms the algorithm's abstract syntax tree
|
|
135
|
+
- **Dataclass "Shattering"**: Decomposes complex state objects into primitive components
|
|
136
|
+
- **Optimization Applications**: Applies domain-specific optimizations for numerical computation
|
|
137
|
+
- **LLVM Integration**: Extracts LLVM IR for low-level algorithmic analysis
|
|
138
|
+
|
|
139
|
+
- **Performance Breakthroughs**:
|
|
140
|
+
- Learn why nearly identical algorithms can have dramatically different performance (`total_countPlus1vsPlusN.py`)
|
|
141
|
+
- See how memory layout and increment strategy impact computation speed
|
|
142
|
+
- Understand the batching technique that yields order-of-magnitude improvements
|
|
143
|
+
|
|
144
|
+
### 4. **Multi-Level Architecture: From Simple API to Full Customization**
|
|
145
|
+
|
|
146
|
+
The package's architecture supports multiple levels of engagement:
|
|
147
|
+
|
|
148
|
+
- **Basic Usage**:
|
|
149
|
+
- Work with the high-level API in `basecamp.py` for standard computations
|
|
150
|
+
- Access OEIS sequence calculations with minimal code
|
|
151
|
+
|
|
152
|
+
- **Algorithm Exploration**:
|
|
153
|
+
- Compare different implementations in the `reference` directory to understand trade-offs
|
|
130
154
|
- Modify the core algorithm in `theDao.py` while preserving its functional approach
|
|
131
155
|
- Configure system-wide settings in `theSSOT.py` to adjust data types and performance characteristics
|
|
132
|
-
- Use utility functions in `beDRY.py` for common operations
|
|
133
156
|
|
|
134
157
|
- **Advanced Transformation**:
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
- `
|
|
138
|
-
- `
|
|
139
|
-
- `
|
|
158
|
+
- Use the `someAssemblyRequired` package to transform algorithms at the AST level
|
|
159
|
+
- Create optimized variants with different compilation settings using:
|
|
160
|
+
- `transformationTools.py` for AST manipulation
|
|
161
|
+
- `transformDataStructures.py` for complex data structure transformations
|
|
162
|
+
- `ingredientsNumba.py` for Numba-specific optimization profiles
|
|
163
|
+
- `synthesizeNumbaFlow.py` to orchestrate the transformation process
|
|
140
164
|
|
|
141
165
|
- **Custom Deployment**:
|
|
142
166
|
- Generate specialized implementations for specific dimensions
|
|
143
|
-
- Create optimized modules
|
|
144
|
-
- Extract LLVM IR for further analysis
|
|
167
|
+
- Create optimized standalone modules for production use
|
|
168
|
+
- Extract LLVM IR for further analysis and optimization
|
|
145
169
|
|
|
146
|
-
The package's multi-level design allows you to start with simple API calls and progressively
|
|
170
|
+
The package's multi-level design allows you to start with simple API calls and progressively explore deeper optimization techniques as your computational needs grow.
|
|
147
171
|
|
|
148
172
|
## Map-folding Video
|
|
149
173
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=hYxPUBU6A1_XCbKEseSDamooTsb1mzN_XHqaRLPvpGk,1701
|
|
2
|
+
mapFolding/basecamp.py,sha256=uPwbb_fi8zqqBbVjb355qanSNUqqJ9aefcf_nrvA7qI,4510
|
|
3
|
+
mapFolding/beDRY.py,sha256=UhH52BryHQNRjphf_PirtMkV45rhdemdC9PmnpACq7I,9397
|
|
4
|
+
mapFolding/filesystem.py,sha256=-pYpWugd0p3TrAz7xf9YIJW-pn1X-iRCGtJgEAF9Rns,5923
|
|
5
|
+
mapFolding/noHomeYet.py,sha256=UKZeWlyn0SKlF9dhYoud7E6gWXpiSEekZOOoJp88WeI,1362
|
|
6
|
+
mapFolding/oeis.py,sha256=TbY8KtAGbQlT6eEsa_7HVMF7bMLN-aBFKclyTMHfqHk,12615
|
|
7
|
+
mapFolding/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
mapFolding/theDao.py,sha256=Blzm5j24x1BE2nvgXjdzHEeuc2na6kAH9b_eP6PcwlI,9836
|
|
9
|
+
mapFolding/theSSOT.py,sha256=HIU9UVKP0dXlxIO791z_tl1R7r5LiEcKnA5AI7H9SmU,12414
|
|
10
|
+
mapFolding/reference/__init__.py,sha256=7NYyWXyYYSdia2Q8SFJTcD0KvoUBLvvfuvTsaqcvchM,1777
|
|
11
|
+
mapFolding/reference/flattened.py,sha256=QK1xG9SllqCoi68e86Hyl9d9ATUAAFNpTQI-3zmcp5I,16072
|
|
12
|
+
mapFolding/reference/hunterNumba.py,sha256=espFiX92EPZ1Ub1YQVoBnNYvh2kFg1HR6Qa4djx8Ixg,7253
|
|
13
|
+
mapFolding/reference/irvineJavaPort.py,sha256=UEfIX4QbPLl5jnyfYIyX5YRR3_rYvPUikK8jLehsFko,4076
|
|
14
|
+
mapFolding/reference/jaxCount.py,sha256=TuDNKOnyhQfuixKmIxO9Algv7dvy7KMGhgsV3h96FGE,14853
|
|
15
|
+
mapFolding/reference/lunnanNumpy.py,sha256=mMgrgbrBpe4nmo72ThEI-MGH0OwEHmfMPczSXHp2qKo,4357
|
|
16
|
+
mapFolding/reference/lunnanWhile.py,sha256=ZL8GAQtPs5nJZSgoDl5USrLSS_zs03y98y1Z9E4jOmQ,3799
|
|
17
|
+
mapFolding/reference/rotatedEntryPoint.py,sha256=5ughpKUT2JQhoAKgoDUdYNjgWQYPGV8v-7dWEAdDmfE,10274
|
|
18
|
+
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=yJZAVLVdoXqHag2_N6_6CT-Q6HXBgRro-eny93-Rlpw,9307
|
|
19
|
+
mapFolding/someAssemblyRequired/__init__.py,sha256=xA5a-nZjXIwcqEOig5PEZSxde4_m3JJ5Pb0CN4aiRjw,2488
|
|
20
|
+
mapFolding/someAssemblyRequired/getLLVMforNoReason.py,sha256=bGI8RZY-RnyR9TNF0r0OXwA6fm4TYH2cHy7WzhsnddQ,1895
|
|
21
|
+
mapFolding/someAssemblyRequired/ingredientsNumba.py,sha256=g6Z7t35NpoDskzm0OLwTQhHw5CYiYktVYxI2NhCQHww,8435
|
|
22
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py,sha256=0179DMPoGJGP6AHs-nIac_aSfVBgpDyuaeCBVKlPRe8,10691
|
|
23
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py,sha256=RBSrtr7US2P7mkY-EA-b2WIOxjs2b0WJaCln1ERxOcI,22314
|
|
24
|
+
mapFolding/someAssemblyRequired/transformDataStructures.py,sha256=Uth-WLzCTJTQkU15lsrBGf1ld7nqgLK42DUYM6-Q-n8,8605
|
|
25
|
+
mapFolding/someAssemblyRequired/transformationTools.py,sha256=n_lH9B7E871htRRRVJIDqGuT4WTmGiIgBb3ZjsY7ZjA,40689
|
|
26
|
+
mapFolding/syntheticModules/numbaCount_doTheNeedful.py,sha256=52RuwJVH2fROvWU2dT8wYcQvLgRuvkNZPq01kujCC_U,15725
|
|
27
|
+
mapfolding-0.8.2.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
28
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
tests/conftest.py,sha256=hTpUTW7MtYGP5aeQnnoZMKgTEGCKdLJ8Fnmnv9d4NJw,11115
|
|
30
|
+
tests/test_computations.py,sha256=RHHByyuC8w-qbaag4Iqo_QNYm_7A-9BslbstMOdbZbU,3329
|
|
31
|
+
tests/test_filesystem.py,sha256=Kou0gj5T72oISao6umYfU6L_W5Hi7QS9_IxTv2hU0Pw,3147
|
|
32
|
+
tests/test_oeis.py,sha256=uxvwmgbnylSDdsVJfuAT0LuYLbIVFwSgdLxHm-xUGBM,5043
|
|
33
|
+
tests/test_other.py,sha256=4iF6JJ192BtDZUEZ0avbVRprCwfCUSOUC8GfCPrGS8M,4232
|
|
34
|
+
tests/test_tasks.py,sha256=hkZygihT8bCEO2zc-2VcxReQrZJBwgLNbYx0YP4lTDg,2853
|
|
35
|
+
mapfolding-0.8.2.dist-info/METADATA,sha256=8tSQtHxSLzIjIINZCw4EExiSeQV_wpLQXvf4gQ4xFNo,9143
|
|
36
|
+
mapfolding-0.8.2.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
|
37
|
+
mapfolding-0.8.2.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
38
|
+
mapfolding-0.8.2.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
39
|
+
mapfolding-0.8.2.dist-info/RECORD,,
|
tests/conftest.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
from importlib import import_module as importlib_import_module
|
|
1
2
|
from collections.abc import Callable, Generator, Sequence
|
|
2
|
-
from
|
|
3
|
+
from types import ModuleType
|
|
4
|
+
|
|
5
|
+
import numpy
|
|
6
|
+
from mapFolding.theSSOT import ComputationState, The, getPackageDispatcher
|
|
3
7
|
from mapFolding.beDRY import getLeavesTotal, validateListDimensions, makeDataContainer
|
|
4
8
|
from mapFolding.oeis import oeisIDsImplemented, settingsOEIS
|
|
5
|
-
from
|
|
9
|
+
from mapFolding.someAssemblyRequired import RecipeSynthesizeFlow
|
|
10
|
+
from mapFolding.someAssemblyRequired.synthesizeNumbaFlow import makeNumbaFlow
|
|
11
|
+
from pathlib import Path, PurePosixPath
|
|
6
12
|
from typing import Any, ContextManager
|
|
7
13
|
import importlib.util
|
|
8
14
|
import pytest
|
|
@@ -13,7 +19,6 @@ import uuid
|
|
|
13
19
|
|
|
14
20
|
# SSOT for test data paths and filenames
|
|
15
21
|
pathDataSamples = Path("tests/dataSamples")
|
|
16
|
-
# NOTE `tmp` is not a diminutive form of temporary: it signals a technical term. And "temp" is strongly disfavored.
|
|
17
22
|
pathTmpRoot: Path = pathDataSamples / "tmp"
|
|
18
23
|
|
|
19
24
|
# The registrar maintains the register of temp files
|
|
@@ -142,7 +147,7 @@ def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.As
|
|
|
142
147
|
def mockFoldingFunction() -> Callable[..., Callable[..., None]]:
|
|
143
148
|
"""Creates a mock function that simulates _countFolds behavior."""
|
|
144
149
|
def make_mock(foldsValue: int, listDimensions: list[int]) -> Callable[..., None]:
|
|
145
|
-
mock_array = makeDataContainer(2)
|
|
150
|
+
mock_array = makeDataContainer(2, numpy.int32)
|
|
146
151
|
mock_array[0] = foldsValue
|
|
147
152
|
mapShape = validateListDimensions(listDimensions)
|
|
148
153
|
mock_array[-1] = getLeavesTotal(mapShape)
|
|
@@ -192,6 +197,11 @@ def useThisDispatcher() -> Generator[Callable[..., None], Any, None]:
|
|
|
192
197
|
yield patchDispatcher
|
|
193
198
|
basecamp.getPackageDispatcher = dispatcherOriginal
|
|
194
199
|
|
|
200
|
+
def getAlgorithmDispatcher() -> Callable[[ComputationState], ComputationState]:
|
|
201
|
+
moduleImported: ModuleType = importlib_import_module(The.logicalPathModuleSourceAlgorithm)
|
|
202
|
+
dispatcherCallable = getattr(moduleImported, The.dispatcherCallable)
|
|
203
|
+
return dispatcherCallable
|
|
204
|
+
|
|
195
205
|
@pytest.fixture
|
|
196
206
|
def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Generator[None, None, None]:
|
|
197
207
|
"""Temporarily patches getDispatcherCallable to return the algorithm dispatcher."""
|
|
@@ -199,35 +209,35 @@ def useAlgorithmSourceDispatcher(useThisDispatcher: Callable[..., Any]) -> Gener
|
|
|
199
209
|
yield
|
|
200
210
|
|
|
201
211
|
@pytest.fixture
|
|
202
|
-
def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any]) -> Callable[..., Any]:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
212
|
+
def syntheticDispatcherFixture(useThisDispatcher: Callable[..., Any], pathTmpTesting: Path) -> Callable[..., Any]:
|
|
213
|
+
"""Generate synthetic Numba-optimized dispatcher module and patch the dispatcher"""
|
|
214
|
+
# Configure synthesis flow to use test directory
|
|
215
|
+
recipeFlow = RecipeSynthesizeFlow(
|
|
216
|
+
pathPackage=PurePosixPath(pathTmpTesting.absolute()),
|
|
217
|
+
Z0Z_flowLogicalPathRoot=None,
|
|
218
|
+
moduleDispatcher="test_dispatcher",
|
|
219
|
+
# Figure out dynamic flow control to synthesized modules https://github.com/hunterhogan/mapFolding/issues/4
|
|
220
|
+
# dispatcherCallable="dispatcherSynthetic",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# Generate optimized module in test directory
|
|
224
|
+
makeNumbaFlow(recipeFlow)
|
|
225
|
+
|
|
226
|
+
# Import synthesized dispatcher
|
|
227
|
+
importlibSpecificationDispatcher = importlib.util.spec_from_file_location(
|
|
228
|
+
recipeFlow.moduleDispatcher,
|
|
229
|
+
Path(recipeFlow.pathFilenameDispatcher),
|
|
230
|
+
)
|
|
231
|
+
if importlibSpecificationDispatcher is None or importlibSpecificationDispatcher.loader is None:
|
|
232
|
+
raise ImportError("Failed to load synthetic dispatcher module")
|
|
233
|
+
|
|
234
|
+
moduleSpecificationDispatcher = importlib.util.module_from_spec(importlibSpecificationDispatcher)
|
|
235
|
+
importlibSpecificationDispatcher.loader.exec_module(moduleSpecificationDispatcher)
|
|
236
|
+
callableDispatcherSynthetic = getattr(moduleSpecificationDispatcher, recipeFlow.dispatcherCallable)
|
|
237
|
+
|
|
238
|
+
# Patch dispatcher and return callable
|
|
239
|
+
useThisDispatcher(callableDispatcherSynthetic)
|
|
240
|
+
return callableDispatcherSynthetic
|
|
231
241
|
|
|
232
242
|
def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
233
243
|
"""Format assertion message for any test comparison."""
|
tests/test_computations.py
CHANGED
|
@@ -23,6 +23,12 @@ def test_aOFn_calculate_value(oeisID: str) -> None:
|
|
|
23
23
|
for n in settingsOEIS[oeisID]['valuesTestValidation']:
|
|
24
24
|
standardizedEqualToCallableReturn(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
|
|
25
25
|
|
|
26
|
+
def test_syntheticParallel(syntheticDispatcherFixture: None, listDimensionsTestParallelization: list[int]):
|
|
27
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestParallelization)), countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
28
|
+
|
|
29
|
+
def test_syntheticSequential(syntheticDispatcherFixture: None, listDimensionsTestCountFolds: list[int]) -> None:
|
|
30
|
+
standardizedEqualToCallableReturn(getFoldsTotalKnown(tuple(listDimensionsTestCountFolds)), countFolds, listDimensionsTestCountFolds)
|
|
31
|
+
|
|
26
32
|
# @pytest.mark.parametrize('pathFilenameTmpTesting', ['.py'], indirect=True)
|
|
27
33
|
# def test_writeJobNumba(listDimensionsTestCountFolds: list[int], pathFilenameTmpTesting: Path) -> None:
|
|
28
34
|
# from mapFolding.syntheticModules import numbaCount
|
|
@@ -44,10 +50,4 @@ def test_aOFn_calculate_value(oeisID: str) -> None:
|
|
|
44
50
|
|
|
45
51
|
# pathFilenameFoldsTotal = getPathFilenameFoldsTotal(listDimensionsTestCountFolds)
|
|
46
52
|
# registrarRecordsTmpObject(pathFilenameFoldsTotal)
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
# def test_syntheticParallel(syntheticDispatcherFixture: None, listDimensionsTestParallelization: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
50
|
-
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestParallelization)], countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
51
|
-
|
|
52
|
-
# def test_syntheticSequential(syntheticDispatcherFixture: None, listDimensionsTestCountFolds: list[int], foldsTotalKnown: dict[tuple[int, ...], int]):
|
|
53
|
-
# standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestCountFolds)], countFolds, listDimensionsTestCountFolds)
|
|
53
|
+
# standardizedEqualToCallableReturn(str(getFoldsTotalKnown(tuple(listDimensionsTestCountFolds))), pathFilenameFoldsTotal.read_text().strip)
|
tests/test_other.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
2
|
from mapFolding.beDRY import getLeavesTotal, setCPUlimit, validateListDimensions
|
|
3
|
+
from mapFolding.theSSOT import The
|
|
3
4
|
from tests.conftest import standardizedEqualToCallableReturn
|
|
4
5
|
from typing import Any, Literal
|
|
5
6
|
from Z0Z_tools import intInnit
|
|
@@ -78,6 +79,5 @@ def testOopsieKwargsie(nameOfTest: str, callablePytest: Callable[[], None]) -> N
|
|
|
78
79
|
(1, 1),
|
|
79
80
|
])
|
|
80
81
|
def test_setCPUlimit(CPUlimit: None | float | bool | Literal[4] | Literal[-2] | Literal[0] | Literal[1], expectedLimit: Any | int) -> None:
|
|
81
|
-
|
|
82
|
-
if concurrencyPackage == 'numba':
|
|
82
|
+
if The.concurrencyPackage == 'numba':
|
|
83
83
|
standardizedEqualToCallableReturn(expectedLimit, setCPUlimit, CPUlimit)
|