mapFolding 0.3.12__py3-none-any.whl → 0.4.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 +40 -38
- mapFolding/basecamp.py +50 -50
- mapFolding/beDRY.py +336 -336
- mapFolding/oeis.py +262 -262
- mapFolding/reference/flattened.py +294 -293
- mapFolding/reference/hunterNumba.py +126 -126
- mapFolding/reference/irvineJavaPort.py +99 -99
- mapFolding/reference/jax.py +153 -153
- mapFolding/reference/lunnan.py +148 -148
- mapFolding/reference/lunnanNumpy.py +115 -115
- mapFolding/reference/lunnanWhile.py +114 -114
- mapFolding/reference/rotatedEntryPoint.py +183 -183
- mapFolding/reference/total_countPlus1vsPlusN.py +203 -203
- mapFolding/someAssemblyRequired/__init__.py +2 -1
- mapFolding/someAssemblyRequired/getLLVMforNoReason.py +12 -12
- mapFolding/someAssemblyRequired/makeJob.py +48 -48
- mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +17 -17
- mapFolding/someAssemblyRequired/synthesizeNumba.py +343 -633
- mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +371 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +150 -0
- mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +75 -0
- mapFolding/syntheticModules/__init__.py +0 -0
- mapFolding/syntheticModules/numba_countInitialize.py +3 -3
- mapFolding/syntheticModules/numba_countParallel.py +3 -3
- mapFolding/syntheticModules/numba_countSequential.py +3 -3
- mapFolding/syntheticModules/numba_doTheNeedful.py +6 -6
- mapFolding/theDao.py +165 -165
- mapFolding/theSSOT.py +176 -172
- mapFolding/theSSOTnumba.py +90 -74
- mapFolding-0.4.0.dist-info/METADATA +122 -0
- mapFolding-0.4.0.dist-info/RECORD +41 -0
- tests/conftest.py +238 -128
- tests/test_oeis.py +80 -80
- tests/test_other.py +137 -224
- tests/test_tasks.py +21 -21
- tests/test_types.py +2 -2
- mapFolding/someAssemblyRequired/synthesizeNumbaHardcoding.py +0 -188
- mapFolding-0.3.12.dist-info/METADATA +0 -155
- mapFolding-0.3.12.dist-info/RECORD +0 -40
- tests/conftest_tmpRegistry.py +0 -62
- tests/conftest_uniformTests.py +0 -53
- {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/LICENSE +0 -0
- {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/WHEEL +0 -0
- {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.3.12.dist-info → mapFolding-0.4.0.dist-info}/top_level.txt +0 -0
tests/test_tasks.py
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
from tests.conftest import *
|
|
2
|
+
from typing import List, Dict, Literal, Tuple
|
|
2
3
|
import pytest
|
|
3
|
-
from typing import List, Dict, Literal, Tuple, Any
|
|
4
4
|
|
|
5
5
|
# TODO add a test. `C` = number of logical cores available. `n = C + 1`. Ensure that `[2,n]` is computed correctly.
|
|
6
6
|
# Or, probably smarter: limit the number of cores, then run a test with C+1.
|
|
7
7
|
|
|
8
8
|
def test_algorithmSourceParallel(listDimensionsTestParallelization: List[int], foldsTotalKnown: Dict[Tuple[int, ...], int], useAlgorithmDirectly: None) -> None:
|
|
9
|
-
|
|
9
|
+
standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestParallelization)], countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
10
10
|
|
|
11
11
|
def test_countFoldsComputationDivisionsInvalid(listDimensionsTestFunctionality: List[int]) -> None:
|
|
12
|
-
|
|
12
|
+
standardizedEqualTo(ValueError, countFolds, listDimensionsTestFunctionality, None, {"wrong": "value"})
|
|
13
13
|
|
|
14
14
|
def test_countFoldsComputationDivisionsMaximum(listDimensionsTestParallelization: List[int], foldsTotalKnown: Dict[Tuple[int, ...], int]) -> None:
|
|
15
|
-
|
|
15
|
+
standardizedEqualTo(foldsTotalKnown[tuple(listDimensionsTestParallelization)], countFolds, listDimensionsTestParallelization, None, 'maximum')
|
|
16
16
|
|
|
17
17
|
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit())
|
|
18
18
|
def test_defineConcurrencyLimit(nameOfTest: str, callablePytest: Callable[[], None]) -> None:
|
|
19
|
-
|
|
19
|
+
callablePytest()
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
@pytest.mark.parametrize("CPUlimitParameter", [{"invalid": True}, ["weird"]])
|
|
22
|
+
def test_countFolds_cpuLimitOopsie(listDimensionsTestFunctionality: List[int], CPUlimitParameter: Dict[str, bool] | List[str]) -> None:
|
|
23
|
+
standardizedEqualTo(ValueError, countFolds, listDimensionsTestFunctionality, None, 'cpu', CPUlimitParameter)
|
|
24
24
|
|
|
25
25
|
@pytest.mark.parametrize("computationDivisions, concurrencyLimit, listDimensions, expectedTaskDivisions", [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
(None, 4, [9, 11], 0),
|
|
27
|
+
("maximum", 4, [7, 11], 77),
|
|
28
|
+
("cpu", 4, [3, 7], 4),
|
|
29
|
+
(["invalid"], 4, [19, 23], ValueError),
|
|
30
|
+
(20, 4, [3,5], ValueError)
|
|
31
31
|
])
|
|
32
32
|
def test_getTaskDivisions(computationDivisions: None | List[str] | Literal['maximum'] | Literal['cpu'] | Literal[20], concurrencyLimit: Literal[4], listDimensions: List[int], expectedTaskDivisions: type[ValueError] | Literal[0] | Literal[77] | Literal[4]) -> None:
|
|
33
|
-
|
|
33
|
+
standardizedEqualTo(expectedTaskDivisions, getTaskDivisions, computationDivisions, concurrencyLimit, None, listDimensions)
|
|
34
34
|
|
|
35
35
|
@pytest.mark.parametrize("expected,parameter", [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
(2, "2"), # string
|
|
37
|
+
(ValueError, [4]), # list
|
|
38
|
+
(ValueError, (2,)), # tuple
|
|
39
|
+
(ValueError, {2}), # set
|
|
40
|
+
(ValueError, {"cores": 2}), # dict
|
|
41
41
|
])
|
|
42
42
|
def test_setCPUlimitMalformedParameter(expected: type[ValueError] | Literal[2], parameter: List[int] | Tuple[int] | set[int] | Dict[str, int] | Literal['2']) -> None:
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
"""Test that invalid CPUlimit types are properly handled."""
|
|
44
|
+
standardizedEqualTo(expected, setCPUlimit, parameter)
|
tests/test_types.py
CHANGED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
from synthesizeNumba import *
|
|
2
|
-
|
|
3
|
-
def makeNumbaOptimizedFlow(listCallablesInline: List[str], callableDispatcher: Optional[str] = None, algorithmSource: Optional[ModuleType] = None) -> None:
|
|
4
|
-
if not algorithmSource:
|
|
5
|
-
algorithmSource = getAlgorithmSource()
|
|
6
|
-
|
|
7
|
-
formatModuleNameDEFAULT = "numba_{callableTarget}"
|
|
8
|
-
|
|
9
|
-
# When I am a more competent programmer, I will make getPathFilenameWrite dependent on makeAstImport or vice versa,
|
|
10
|
-
# so the name of the physical file doesn't get out of whack with the name of the logical module.
|
|
11
|
-
def getPathFilenameWrite(callableTarget: str
|
|
12
|
-
, pathWrite: Optional[pathlib.Path] = None
|
|
13
|
-
, formatFilenameWrite: Optional[str] = None
|
|
14
|
-
) -> pathlib.Path:
|
|
15
|
-
if not pathWrite:
|
|
16
|
-
pathWrite = getPathSyntheticModules()
|
|
17
|
-
if not formatFilenameWrite:
|
|
18
|
-
formatFilenameWrite = formatModuleNameDEFAULT + '.py'
|
|
19
|
-
|
|
20
|
-
pathFilename = pathWrite / formatFilenameWrite.format(callableTarget=callableTarget)
|
|
21
|
-
return pathFilename
|
|
22
|
-
|
|
23
|
-
def makeAstImport(callableTarget: str
|
|
24
|
-
, packageName: Optional[str] = None
|
|
25
|
-
, subPackageName: Optional[str] = None
|
|
26
|
-
, moduleName: Optional[str] = None
|
|
27
|
-
, astNodeLogicalPathThingy: Optional[ast.AST] = None
|
|
28
|
-
) -> ast.ImportFrom:
|
|
29
|
-
"""Creates import AST node for synthetic modules."""
|
|
30
|
-
if astNodeLogicalPathThingy is None:
|
|
31
|
-
if packageName is None:
|
|
32
|
-
packageName = myPackageNameIs
|
|
33
|
-
if subPackageName is None:
|
|
34
|
-
subPackageName = moduleOfSyntheticModules
|
|
35
|
-
if moduleName is None:
|
|
36
|
-
moduleName = formatModuleNameDEFAULT.format(callableTarget=callableTarget)
|
|
37
|
-
module=f'{packageName}.{subPackageName}.{moduleName}'
|
|
38
|
-
else:
|
|
39
|
-
module = str(astNodeLogicalPathThingy)
|
|
40
|
-
return ast.ImportFrom(
|
|
41
|
-
module=module,
|
|
42
|
-
names=[ast.alias(name=callableTarget, asname=None)],
|
|
43
|
-
level=0
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
listStuffYouOughtaKnow: List[youOughtaKnow] = []
|
|
47
|
-
|
|
48
|
-
for callableTarget in listCallablesInline:
|
|
49
|
-
pythonSource = inspect.getsource(algorithmSource)
|
|
50
|
-
parametersNumba = None
|
|
51
|
-
unpackArrays = False
|
|
52
|
-
match callableTarget:
|
|
53
|
-
case 'countParallel':
|
|
54
|
-
parametersNumba = parametersNumbaSuperJitParallel
|
|
55
|
-
case 'countSequential':
|
|
56
|
-
parametersNumba = parametersNumbaSuperJit
|
|
57
|
-
unpackArrays = True
|
|
58
|
-
case 'countInitialize':
|
|
59
|
-
parametersNumba = parametersNumbaDEFAULT
|
|
60
|
-
pythonSource = inlineOneCallable(pythonSource, callableTarget, parametersNumba, unpackArrays)
|
|
61
|
-
if not pythonSource:
|
|
62
|
-
raise Exception("Pylance, OMG! The sky is falling!")
|
|
63
|
-
|
|
64
|
-
pathFilename = getPathFilenameWrite(callableTarget)
|
|
65
|
-
|
|
66
|
-
listStuffYouOughtaKnow.append(youOughtaKnow(
|
|
67
|
-
callableSynthesized=callableTarget,
|
|
68
|
-
pathFilenameForMe=pathFilename,
|
|
69
|
-
astForCompetentProgrammers=makeAstImport(callableTarget)
|
|
70
|
-
))
|
|
71
|
-
pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
|
|
72
|
-
pathFilename.write_text(pythonSource)
|
|
73
|
-
|
|
74
|
-
# Generate dispatcher if requested
|
|
75
|
-
if callableDispatcher:
|
|
76
|
-
pythonSource = inspect.getsource(algorithmSource)
|
|
77
|
-
pythonSource = makeDispatcherNumba(pythonSource, callableDispatcher, listStuffYouOughtaKnow)
|
|
78
|
-
if not pythonSource:
|
|
79
|
-
raise FREAKOUT
|
|
80
|
-
|
|
81
|
-
pathFilename = getPathFilenameWrite(callableDispatcher)
|
|
82
|
-
|
|
83
|
-
listStuffYouOughtaKnow.append(youOughtaKnow(
|
|
84
|
-
callableSynthesized=callableDispatcher,
|
|
85
|
-
pathFilenameForMe=pathFilename,
|
|
86
|
-
astForCompetentProgrammers=makeAstImport(callableDispatcher)
|
|
87
|
-
))
|
|
88
|
-
pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
|
|
89
|
-
pathFilename.write_text(pythonSource)
|
|
90
|
-
|
|
91
|
-
def writeJobNumba(listDimensions: Sequence[int], callableTarget: str, algorithmSource: ModuleType, parametersNumba: Optional[ParametersNumba]=None, pathFilenameWriteJob: Optional[Union[str, os.PathLike[str]]] = None, **keywordArguments: Optional[Any]) -> pathlib.Path:
|
|
92
|
-
""" Parameters: **keywordArguments: most especially for `computationDivisions` if you want to make a parallel job. Also `CPUlimit`. """
|
|
93
|
-
"""Notes about the existing logic:
|
|
94
|
-
- the synthesized module must run well as a standalone interpreted Python script
|
|
95
|
-
- `writeJobNumba` synthesizes a parameter-specific module by starting with code synthesized by `makeNumbaOptimizedFlow`, which improves the optimization
|
|
96
|
-
- similarly, `writeJobNumba` should be a solid foundation for more optimizations, most especially compiling to a standalone executable, but the details of the next optimization step are unknown
|
|
97
|
-
- the minimum runtime (on my computer) to compute a value unknown to mathematicians is 26 hours, therefore, we ant to ensure the value is seen by the user, but we must have ultra-light overhead.
|
|
98
|
-
- perf_counter is for testing. When I run a real job, I delete those lines
|
|
99
|
-
- avoid `with` statement
|
|
100
|
-
"""
|
|
101
|
-
stateJob = makeStateJob(listDimensions, writeJob=False, **keywordArguments)
|
|
102
|
-
pythonSource = inspect.getsource(algorithmSource)
|
|
103
|
-
astModule = ast.parse(pythonSource)
|
|
104
|
-
|
|
105
|
-
allImports = UniversalImportTracker()
|
|
106
|
-
|
|
107
|
-
for statement in astModule.body:
|
|
108
|
-
if isinstance(statement, (ast.Import, ast.ImportFrom)):
|
|
109
|
-
allImports.addAst(statement)
|
|
110
|
-
|
|
111
|
-
FunctionDefTarget = next((node for node in astModule.body if isinstance(node, ast.FunctionDef) and node.name == callableTarget), None)
|
|
112
|
-
if not FunctionDefTarget: raise ValueError(f"I received `{callableTarget=}` and {algorithmSource.__name__=}, but I could not find that function in that source.")
|
|
113
|
-
|
|
114
|
-
for pirateScowl in FunctionDefTarget.args.args.copy():
|
|
115
|
-
match pirateScowl.arg:
|
|
116
|
-
case 'my':
|
|
117
|
-
FunctionDefTarget, allImports = evaluate_argIn_body(FunctionDefTarget, pirateScowl, stateJob[pirateScowl.arg], ['taskIndex', 'dimensionsTotal'], allImports)
|
|
118
|
-
case 'track':
|
|
119
|
-
FunctionDefTarget, allImports = evaluateArrayIn_body(FunctionDefTarget, pirateScowl, stateJob[pirateScowl.arg], allImports)
|
|
120
|
-
# TODO remove this after implementing `unrollWhileLoop`
|
|
121
|
-
case 'connectionGraph':
|
|
122
|
-
FunctionDefTarget, allImports = moveArrayTo_body(FunctionDefTarget, pirateScowl, stateJob[pirateScowl.arg], allImports)
|
|
123
|
-
case 'gapsWhere':
|
|
124
|
-
FunctionDefTarget, allImports = moveArrayTo_body(FunctionDefTarget, pirateScowl, stateJob[pirateScowl.arg], allImports)
|
|
125
|
-
case 'foldGroups':
|
|
126
|
-
FunctionDefTarget = removeIdentifierFrom_body(FunctionDefTarget, pirateScowl)
|
|
127
|
-
|
|
128
|
-
# Move function parameters to the function body,
|
|
129
|
-
# initialize identifiers with their state types and values,
|
|
130
|
-
# and replace static-valued identifiers with their values.
|
|
131
|
-
FunctionDefTarget, allImports = evaluateAnnAssignIn_body(FunctionDefTarget, allImports)
|
|
132
|
-
FunctionDefTarget = astNameToAstConstant(FunctionDefTarget, 'dimensionsTotal', int(stateJob['my'][indexMy.dimensionsTotal]))
|
|
133
|
-
FunctionDefTarget = astObjectToAstConstant(FunctionDefTarget, 'foldGroups[-1]', int(stateJob['foldGroups'][-1]))
|
|
134
|
-
|
|
135
|
-
FunctionDefTarget = unrollWhileLoop(FunctionDefTarget, 'indexDimension', stateJob['my'][indexMy.dimensionsTotal], stateJob['connectionGraph'])
|
|
136
|
-
|
|
137
|
-
FunctionDefTarget, allImports = addReturnJobNumba(FunctionDefTarget, stateJob, allImports)
|
|
138
|
-
FunctionDefTarget, allImports = makeDecoratorJobNumba(FunctionDefTarget, allImports, parametersNumba)
|
|
139
|
-
|
|
140
|
-
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateJob['mapShape'])
|
|
141
|
-
# TODO consider: 1) launcher is a function, 2) if __name__ calls the launcher function, and 3) the launcher is "jitted", even just a light jit, then 4) `FunctionDefTarget` could be superJit.
|
|
142
|
-
astLauncher = makeLauncherJobNumba(FunctionDefTarget.name, pathFilenameFoldsTotal)
|
|
143
|
-
|
|
144
|
-
astImports = allImports.makeListAst()
|
|
145
|
-
|
|
146
|
-
astModule = ast.Module(body=cast(List[ast.stmt], astImports + [FunctionDefTarget] + [astLauncher]), type_ignores=[])
|
|
147
|
-
ast.fix_missing_locations(astModule)
|
|
148
|
-
|
|
149
|
-
pythonSource = ast.unparse(astModule)
|
|
150
|
-
pythonSource = autoflake.fix_code(pythonSource, ['mapFolding', 'numba', 'numpy'])
|
|
151
|
-
|
|
152
|
-
if pathFilenameWriteJob is None:
|
|
153
|
-
filename = getFilenameFoldsTotal(stateJob['mapShape'])
|
|
154
|
-
pathRoot = getPathJobRootDEFAULT()
|
|
155
|
-
pathFilenameWriteJob = pathlib.Path(pathRoot, pathlib.Path(filename).stem, pathlib.Path(filename).with_suffix('.py'))
|
|
156
|
-
else:
|
|
157
|
-
pathFilenameWriteJob = pathlib.Path(pathFilenameWriteJob)
|
|
158
|
-
pathFilenameWriteJob.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
-
|
|
160
|
-
pathFilenameWriteJob.write_text(pythonSource)
|
|
161
|
-
return pathFilenameWriteJob
|
|
162
|
-
|
|
163
|
-
def mainBig():
|
|
164
|
-
setDatatypeModule('numpy', sourGrapes=True)
|
|
165
|
-
setDatatypeFoldsTotal('int64', sourGrapes=True)
|
|
166
|
-
setDatatypeElephino('uint8', sourGrapes=True)
|
|
167
|
-
setDatatypeLeavesTotal('uint8', sourGrapes=True)
|
|
168
|
-
listCallablesInline: List[str] = ['countInitialize', 'countParallel', 'countSequential']
|
|
169
|
-
Z0Z_setDatatypeModuleScalar('numba')
|
|
170
|
-
Z0Z_setDecoratorCallable('jit')
|
|
171
|
-
callableDispatcher = 'doTheNeedful'
|
|
172
|
-
makeNumbaOptimizedFlow(listCallablesInline, callableDispatcher)
|
|
173
|
-
|
|
174
|
-
def mainSmall():
|
|
175
|
-
listDimensions = [6,6]
|
|
176
|
-
setDatatypeFoldsTotal('int64', sourGrapes=True)
|
|
177
|
-
setDatatypeElephino('uint8', sourGrapes=True)
|
|
178
|
-
setDatatypeLeavesTotal('uint8', sourGrapes=True)
|
|
179
|
-
from mapFolding.syntheticModules import numba_countSequential
|
|
180
|
-
algorithmSource: ModuleType = numba_countSequential
|
|
181
|
-
Z0Z_setDatatypeModuleScalar('numba')
|
|
182
|
-
Z0Z_setDecoratorCallable('jit')
|
|
183
|
-
writeJobNumba(listDimensions, 'countSequential', algorithmSource, parametersNumbaDEFAULT)
|
|
184
|
-
|
|
185
|
-
if __name__ == '__main__':
|
|
186
|
-
mainBig()
|
|
187
|
-
|
|
188
|
-
mainSmall()
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: mapFolding
|
|
3
|
-
Version: 0.3.12
|
|
4
|
-
Summary: Count distinct ways to fold a map (or a strip of stamps)
|
|
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
|
-
Keywords: A001415,A001416,A001417,A001418,A195646,folding,map folding,OEIS,stamp folding
|
|
11
|
-
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
-
Classifier: Environment :: Console
|
|
13
|
-
Classifier: Intended Audience :: Education
|
|
14
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
-
Classifier: Intended Audience :: Other Audience
|
|
16
|
-
Classifier: Intended Audience :: Science/Research
|
|
17
|
-
Classifier: Natural Language :: English
|
|
18
|
-
Classifier: Operating System :: OS Independent
|
|
19
|
-
Classifier: Programming Language :: Python
|
|
20
|
-
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
-
Classifier: Typing :: Typed
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: numba
|
|
26
|
-
Requires-Dist: numpy
|
|
27
|
-
Requires-Dist: Z0Z_tools
|
|
28
|
-
Provides-Extra: testing
|
|
29
|
-
Requires-Dist: autoflake; extra == "testing"
|
|
30
|
-
Requires-Dist: more_itertools; extra == "testing"
|
|
31
|
-
Requires-Dist: mypy; extra == "testing"
|
|
32
|
-
Requires-Dist: pytest-cov; extra == "testing"
|
|
33
|
-
Requires-Dist: pytest-env; extra == "testing"
|
|
34
|
-
Requires-Dist: pytest-mypy; extra == "testing"
|
|
35
|
-
Requires-Dist: pytest-xdist; extra == "testing"
|
|
36
|
-
Requires-Dist: pytest; extra == "testing"
|
|
37
|
-
Requires-Dist: python_minifier; extra == "testing"
|
|
38
|
-
Requires-Dist: types-setuptools; extra == "testing"
|
|
39
|
-
Requires-Dist: updateCitation; extra == "testing"
|
|
40
|
-
|
|
41
|
-
# Algorithm(s) for counting distinct ways to fold a map (or a strip of stamps)
|
|
42
|
-
|
|
43
|
-
The function `mapFolding.countFolds()` counts distinct ways to fold maps and strips of stamps. The function accepts two or more dimensions:
|
|
44
|
-
|
|
45
|
-
```python
|
|
46
|
-
from mapFolding import countFolds
|
|
47
|
-
foldsTotal = countFolds( [2,10] )
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
The directory [mapFolding/reference](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference) has
|
|
51
|
-
|
|
52
|
-
- a verbatim transcription of Lunnon's "procedure" published in 1971 by _The Computer Journal_,
|
|
53
|
-
- multiple referential versions of the procedure with explanatory comments including
|
|
54
|
-
- [hunterNumba.py](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference), a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
|
|
55
|
-
- miscellaneous notes.
|
|
56
|
-
|
|
57
|
-
[](https://pypi.org/project/mapFolding/) [](https://github.com/hunterhogan/mapFolding/actions/workflows/pythonTests.yml) [](https://youtu.be/g6f_miE91mk&t=4)   
|
|
58
|
-
|
|
59
|
-
## Simple, easy usage based on OEIS IDs
|
|
60
|
-
|
|
61
|
-
`mapFolding` directly implements some IDs from _The On-Line Encyclopedia of Integer Sequences_ ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/oeis.bibtex) citation).
|
|
62
|
-
|
|
63
|
-
### Usage: command line
|
|
64
|
-
|
|
65
|
-
After installing (see below), `OEIS_for_n` will run a computation from the command line.
|
|
66
|
-
|
|
67
|
-
```cmd
|
|
68
|
-
(mapFolding) C:\apps\mapFolding> OEIS_for_n A001418 5
|
|
69
|
-
186086600 distinct folding patterns.
|
|
70
|
-
Time elapsed: 1.605 seconds
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Use `getOEISids` to get the most up-to-date list of available OEIS IDs.
|
|
74
|
-
|
|
75
|
-
```cmd
|
|
76
|
-
(mapFolding) C:\apps\mapFolding> getOEISids
|
|
77
|
-
|
|
78
|
-
Available OEIS sequences:
|
|
79
|
-
A001415: Number of ways of folding a 2 X n strip of stamps.
|
|
80
|
-
A001416: Number of ways of folding a 3 X n strip of stamps.
|
|
81
|
-
A001417: Number of ways of folding a 2 X 2 X ... X 2 n-dimensional map.
|
|
82
|
-
A001418: Number of ways of folding an n X n sheet of stamps.
|
|
83
|
-
A195646: Number of ways of folding a 3 X 3 X ... X 3 n-dimensional map.
|
|
84
|
-
|
|
85
|
-
Usage examples:
|
|
86
|
-
Command line:
|
|
87
|
-
OEIS_for_n A001415 8
|
|
88
|
-
Python:
|
|
89
|
-
from mapFolding import oeisIDfor_n
|
|
90
|
-
foldsTotal = oeisIDfor_n('A001415', 8)
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Usage: Python module or REPL
|
|
94
|
-
|
|
95
|
-
Use `mapFolding.oeisIDfor_n()` to compute a(n) for an OEIS ID.
|
|
96
|
-
|
|
97
|
-
```python
|
|
98
|
-
from mapFolding import oeisIDfor_n
|
|
99
|
-
foldsTotal = oeisIDfor_n( 'A001418', 4 )
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### An "advanced" and likely unnecessary feature: clear `mapFolding`'s cache of OEIS data
|
|
103
|
-
|
|
104
|
-
Clear _The On-Line Encyclopedia of Integer Sequences_ data from the `mapFolding` cache:
|
|
105
|
-
|
|
106
|
-
```sh
|
|
107
|
-
(mapFolding) C:\apps\mapFolding> clearOEIScache
|
|
108
|
-
Cache cleared from C:\apps\mapFolding\mapFolding\.cache
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Connections to "Multi-dimensional map-folding" by W. F. Lunnon
|
|
112
|
-
|
|
113
|
-
### The typo-laden algorithm published in 1971
|
|
114
|
-
|
|
115
|
-
The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
|
|
116
|
-
|
|
117
|
-
In [`foldings.txt`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
|
|
118
|
-
|
|
119
|
-
### Java implementation(s) and improvements
|
|
120
|
-
|
|
121
|
-
[archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
|
|
122
|
-
|
|
123
|
-
```java
|
|
124
|
-
/**
|
|
125
|
-
* A001415 Number of ways of folding a 2 X n strip of stamps.
|
|
126
|
-
* @author Fred Lunnon (ALGOL68, C versions)
|
|
127
|
-
* @author Sean A. Irvine (Java port)
|
|
128
|
-
*/
|
|
129
|
-
...
|
|
130
|
-
// Implements algorithm as described in "Multi-dimensional map-folding",
|
|
131
|
-
// by W. F. Lunnon, The Computer J, 14, 1, pp. 75--80. Note the original
|
|
132
|
-
// paper contains a few omissions, so this actual code is based on a C
|
|
133
|
-
// implementation by Fred Lunnon.
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Map-folding Video
|
|
137
|
-
|
|
138
|
-
~~This caused my neurosis:~~ I enjoyed the following video, which is what introduced me to map folding.
|
|
139
|
-
|
|
140
|
-
"How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
|
|
141
|
-
|
|
142
|
-
[](https://www.youtube.com/watch?v=sfH9uIY3ln4)
|
|
143
|
-
|
|
144
|
-
## Installation
|
|
145
|
-
|
|
146
|
-
```sh
|
|
147
|
-
pip install mapFolding
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## My recovery
|
|
151
|
-
|
|
152
|
-
[](https://HunterThinks.com/support)
|
|
153
|
-
[](https://www.youtube.com/@HunterHogan)
|
|
154
|
-
|
|
155
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
mapFolding/__init__.py,sha256=XYw6cd3gQLFxoMuSSHwScp9-uFUU5B22Edy2pHNULJ8,1384
|
|
2
|
-
mapFolding/basecamp.py,sha256=DISWQIzAF7o7WPUn2QZxMu3Ruwc0zkVPgceeUTUcfqo,3988
|
|
3
|
-
mapFolding/beDRY.py,sha256=Gsj7NEBztbPoKszPibhW6pO9xwjAIPgaW411A_Quwz8,18654
|
|
4
|
-
mapFolding/oeis.py,sha256=2kFIAnBu0Bs4rHceXqs1RyyEBM6A_5fR5562B4ShxVg,12311
|
|
5
|
-
mapFolding/theDao.py,sha256=qzZgXqI68NeKevMNZSFTnar4NsyploGTl-_Wwo8P-2s,14274
|
|
6
|
-
mapFolding/theSSOT.py,sha256=hl6VUR3P7e6JF3L4rHI_icnmNR1gJJUAI2rCvlQmCDs,10577
|
|
7
|
-
mapFolding/theSSOTnumba.py,sha256=SXPa9Qkr0IUFBb3k5__088IAS0imWHG8Aui2S27XbuE,5689
|
|
8
|
-
mapFolding/reference/flattened.py,sha256=6blZ2Y9G8mu1F3gV8SKndPE398t2VVFlsgKlyeJ765A,16538
|
|
9
|
-
mapFolding/reference/hunterNumba.py,sha256=HWndRgsajOf76rbb2LDNEZ6itsdYbyV-k3wgOFjeR6c,7104
|
|
10
|
-
mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
|
|
11
|
-
mapFolding/reference/jax.py,sha256=rojyK80lOATtbzxjGOHWHZngQa47CXCLJHZwIdN2MwI,14955
|
|
12
|
-
mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
|
|
13
|
-
mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
|
|
14
|
-
mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
|
|
15
|
-
mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
|
|
16
|
-
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
|
|
17
|
-
mapFolding/someAssemblyRequired/__init__.py,sha256=3JnAKXfaYPtmxV_4AnZ6KpCosT_0GFV5Nw7K8sz4-Uo,34
|
|
18
|
-
mapFolding/someAssemblyRequired/getLLVMforNoReason.py,sha256=FtJzw2pZS3A4NimWdZsegXaU-vKeCw8m67kcfb5wvGM,894
|
|
19
|
-
mapFolding/someAssemblyRequired/makeJob.py,sha256=2RtRAHBMDAiOsJpnj1-J1Yso3beQu5hk8bUfgE367Zs,2592
|
|
20
|
-
mapFolding/someAssemblyRequired/synthesizeModuleJAX.py,sha256=TU8lgoMr77pctvvmlfbT-XstQk4j6Jo8JcIuvAiCuMU,1316
|
|
21
|
-
mapFolding/someAssemblyRequired/synthesizeNumba.py,sha256=6wQnKLj2hbj-z4enypYoDpS6LcrE6lMDYUQ8eXquv_o,33476
|
|
22
|
-
mapFolding/someAssemblyRequired/synthesizeNumbaHardcoding.py,sha256=aOATx1_cUcd7pb6YhdDe9ydL0QB6538NoFgyPKXzYpg,9994
|
|
23
|
-
mapFolding/syntheticModules/numba_countInitialize.py,sha256=Cpk3HETqlQhyX431RzmvBSgKOx7nX9ZC4iCyBYHldMA,4274
|
|
24
|
-
mapFolding/syntheticModules/numba_countParallel.py,sha256=5qrYl8InirBjzQKQh9eny7ajCU-pHk-oZS11alOxhJs,5517
|
|
25
|
-
mapFolding/syntheticModules/numba_countSequential.py,sha256=oMJ9W_ob9sIE-7xfFkFCjG83MeylJJCjmL3_xWHMUdk,3732
|
|
26
|
-
mapFolding/syntheticModules/numba_doTheNeedful.py,sha256=xbKeYWACpbVMNitWPQeTr8l-YY6Y228Lu_JRDjsAPfE,1369
|
|
27
|
-
tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
|
|
28
|
-
tests/conftest.py,sha256=tGRYkHfwVKGoMklBSQiTD8cTu-QL1wUSZd-52Xlnm50,7676
|
|
29
|
-
tests/conftest_tmpRegistry.py,sha256=0XpGe7s2aJcjdEAqKs10vceW0_JAaK-Rp1UoPaL-BIo,2450
|
|
30
|
-
tests/conftest_uniformTests.py,sha256=2v9UJbKgbrus3UyWXsanEHLksBskbnP0MD8iKVPaIBo,2178
|
|
31
|
-
tests/test_oeis.py,sha256=F89HJ27y54NXcR2wMeBS4ea6R7EaEqpF_GkcU-BrsK4,5694
|
|
32
|
-
tests/test_other.py,sha256=UpS0_WUfWA3wONuZwip5AMmzNmmBwYD7reQDZ9ziL_o,12224
|
|
33
|
-
tests/test_tasks.py,sha256=ap_tKjHUN95vjzKo3xzBAQ3kMbdMJ_XXbOv9YIBJ5pY,2826
|
|
34
|
-
tests/test_types.py,sha256=HklNCGThFiqQ89AOMkE7YkcfAPiZE32DpD3GMDUPQVc,177
|
|
35
|
-
mapFolding-0.3.12.dist-info/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
36
|
-
mapFolding-0.3.12.dist-info/METADATA,sha256=y4-lXqgLwjgfGI3mlRHcNsaz3VjiGAsF3RpIFNLcj7g,7785
|
|
37
|
-
mapFolding-0.3.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
38
|
-
mapFolding-0.3.12.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
39
|
-
mapFolding-0.3.12.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
40
|
-
mapFolding-0.3.12.dist-info/RECORD,,
|
tests/conftest_tmpRegistry.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
from typing import Any, Generator, Set
|
|
2
|
-
import pathlib
|
|
3
|
-
import pytest
|
|
4
|
-
import shutil
|
|
5
|
-
import uuid
|
|
6
|
-
|
|
7
|
-
# SSOT for test data paths
|
|
8
|
-
pathDataSamples = pathlib.Path("tests/dataSamples")
|
|
9
|
-
pathTempRoot = pathDataSamples / "tmp"
|
|
10
|
-
|
|
11
|
-
# The registrar maintains the register of temp files
|
|
12
|
-
registerOfTempFiles: Set[pathlib.Path] = set()
|
|
13
|
-
|
|
14
|
-
def addTempFileToRegister(path: pathlib.Path) -> None:
|
|
15
|
-
"""The registrar adds a temp file to the register."""
|
|
16
|
-
registerOfTempFiles.add(path)
|
|
17
|
-
|
|
18
|
-
def cleanupTempFileRegister() -> None:
|
|
19
|
-
"""The registrar cleans up temp files in the register."""
|
|
20
|
-
for pathTemp in sorted(registerOfTempFiles, reverse=True):
|
|
21
|
-
try:
|
|
22
|
-
if pathTemp.is_file():
|
|
23
|
-
pathTemp.unlink(missing_ok=True)
|
|
24
|
-
elif pathTemp.is_dir():
|
|
25
|
-
shutil.rmtree(pathTemp, ignore_errors=True)
|
|
26
|
-
except Exception as ERRORmessage:
|
|
27
|
-
print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
|
|
28
|
-
registerOfTempFiles.clear()
|
|
29
|
-
|
|
30
|
-
@pytest.fixture(scope="session", autouse=True)
|
|
31
|
-
def setupTeardownTestData() -> Generator[None, None, None]:
|
|
32
|
-
"""Auto-fixture to setup test data directories and cleanup after."""
|
|
33
|
-
pathDataSamples.mkdir(exist_ok=True)
|
|
34
|
-
pathTempRoot.mkdir(exist_ok=True)
|
|
35
|
-
yield
|
|
36
|
-
cleanupTempFileRegister()
|
|
37
|
-
|
|
38
|
-
@pytest.fixture
|
|
39
|
-
def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
|
|
40
|
-
"""Create a unique temp directory for each test function."""
|
|
41
|
-
# TODO I got rid of this shit. how the fuck is it back?
|
|
42
|
-
# Sanitize test name for filesystem compatibility
|
|
43
|
-
sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
|
|
44
|
-
uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
|
|
45
|
-
pathTemp = pathTempRoot / uniqueDirectory
|
|
46
|
-
pathTemp.mkdir(parents=True, exist_ok=True)
|
|
47
|
-
|
|
48
|
-
addTempFileToRegister(pathTemp)
|
|
49
|
-
return pathTemp
|
|
50
|
-
|
|
51
|
-
@pytest.fixture
|
|
52
|
-
def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
53
|
-
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
54
|
-
from mapFolding import oeis as there_must_be_a_better_way
|
|
55
|
-
pathCacheOriginal = there_must_be_a_better_way._pathCache
|
|
56
|
-
there_must_be_a_better_way._pathCache = pathTempTesting
|
|
57
|
-
yield pathTempTesting
|
|
58
|
-
there_must_be_a_better_way._pathCache = pathCacheOriginal
|
|
59
|
-
|
|
60
|
-
@pytest.fixture
|
|
61
|
-
def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
|
|
62
|
-
return pathTempTesting.joinpath("foldsTotalTest.txt")
|
tests/conftest_uniformTests.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
from typing import Any, Callable, Sequence, Type, Union
|
|
2
|
-
import pytest
|
|
3
|
-
|
|
4
|
-
def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
5
|
-
"""Format assertion message for any test comparison."""
|
|
6
|
-
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
7
|
-
f"Expected: {expected}\n"
|
|
8
|
-
f"Got: {actual}")
|
|
9
|
-
|
|
10
|
-
def standardizedEqualTo(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
|
|
11
|
-
"""Template for tests expecting an error."""
|
|
12
|
-
if type(expected) is Type[Exception]:
|
|
13
|
-
messageExpected = expected.__name__
|
|
14
|
-
else:
|
|
15
|
-
messageExpected = expected
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
messageActual = actual = functionTarget(*arguments)
|
|
19
|
-
except Exception as actualError:
|
|
20
|
-
messageActual = type(actualError).__name__
|
|
21
|
-
actual = type(actualError)
|
|
22
|
-
|
|
23
|
-
assert actual == expected, uniformTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
|
|
24
|
-
|
|
25
|
-
def standardizedSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: Callable, *arguments: Any) -> None:
|
|
26
|
-
"""Template for tests expecting SystemExit.
|
|
27
|
-
|
|
28
|
-
Parameters
|
|
29
|
-
expected: Exit code expectation:
|
|
30
|
-
- "error": any non-zero exit code
|
|
31
|
-
- "nonError": specifically zero exit code
|
|
32
|
-
- int: exact exit code match
|
|
33
|
-
- Sequence[int]: exit code must be one of these values
|
|
34
|
-
functionTarget: The function to test
|
|
35
|
-
arguments: Arguments to pass to the function
|
|
36
|
-
"""
|
|
37
|
-
with pytest.raises(SystemExit) as exitInfo:
|
|
38
|
-
functionTarget(*arguments)
|
|
39
|
-
|
|
40
|
-
exitCode = exitInfo.value.code
|
|
41
|
-
|
|
42
|
-
if expected == "error":
|
|
43
|
-
assert exitCode != 0, \
|
|
44
|
-
f"Expected error exit (non-zero) but got code {exitCode}"
|
|
45
|
-
elif expected == "nonError":
|
|
46
|
-
assert exitCode == 0, \
|
|
47
|
-
f"Expected non-error exit (0) but got code {exitCode}"
|
|
48
|
-
elif isinstance(expected, (list, tuple)):
|
|
49
|
-
assert exitCode in expected, \
|
|
50
|
-
f"Expected exit code to be one of {expected} but got {exitCode}"
|
|
51
|
-
else:
|
|
52
|
-
assert exitCode == expected, \
|
|
53
|
-
f"Expected exit code {expected} but got {exitCode}"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|