mapFolding 0.3.11__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 +44 -32
- 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 +345 -803
- 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 +2 -2
- mapFolding/syntheticModules/numba_countParallel.py +3 -3
- mapFolding/syntheticModules/numba_countSequential.py +28 -28
- mapFolding/syntheticModules/numba_doTheNeedful.py +6 -6
- mapFolding/theDao.py +168 -169
- mapFolding/theSSOT.py +190 -162
- mapFolding/theSSOTnumba.py +91 -75
- 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-0.3.11.dist-info/METADATA +0 -155
- mapFolding-0.3.11.dist-info/RECORD +0 -39
- tests/conftest_tmpRegistry.py +0 -62
- tests/conftest_uniformTests.py +0 -53
- {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/LICENSE +0 -0
- {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/WHEEL +0 -0
- {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: mapFolding
|
|
3
|
+
Version: 0.4.0
|
|
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: synthesizemodules
|
|
29
|
+
Requires-Dist: autoflake; extra == "synthesizemodules"
|
|
30
|
+
Requires-Dist: more_itertools; extra == "synthesizemodules"
|
|
31
|
+
Requires-Dist: python_minifier; extra == "synthesizemodules"
|
|
32
|
+
Provides-Extra: testing
|
|
33
|
+
Requires-Dist: mypy; extra == "testing"
|
|
34
|
+
Requires-Dist: pytest-cov; extra == "testing"
|
|
35
|
+
Requires-Dist: pytest-env; extra == "testing"
|
|
36
|
+
Requires-Dist: pytest-mypy; extra == "testing"
|
|
37
|
+
Requires-Dist: pytest-xdist; extra == "testing"
|
|
38
|
+
Requires-Dist: pytest; extra == "testing"
|
|
39
|
+
Requires-Dist: updateCitation; extra == "testing"
|
|
40
|
+
|
|
41
|
+
# mapFolding: Algorithms for enumerating distinct map/stamp folding patterns 🗺️
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/mapFolding/)
|
|
44
|
+
[](https://youtu.be/g6f_miE91mk&t=4)
|
|
45
|
+
[](https://github.com/hunterhogan/mapFolding/actions/workflows/pythonTests.yml)
|
|
46
|
+

|
|
47
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
48
|
+

|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pip install mapFolding
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`OEIS_for_n` will run a computation from the command line.
|
|
60
|
+
|
|
61
|
+
```cmd
|
|
62
|
+
(mapFolding) C:\apps\mapFolding> OEIS_for_n A001418 5
|
|
63
|
+
186086600 distinct folding patterns.
|
|
64
|
+
Time elapsed: 1.605 seconds
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use `mapFolding.oeisIDfor_n()` to compute a(n) for an OEIS ID.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from mapFolding import oeisIDfor_n
|
|
71
|
+
foldsTotal = oeisIDfor_n( 'A001418', 4 )
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
### 1. Simple, easy usage based on OEIS IDs
|
|
79
|
+
|
|
80
|
+
`mapFolding` directly implements some IDs from [_The On-Line Encyclopedia of Integer Sequences_](https://oeis.org/) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/citations/oeis.bibtex) citation).
|
|
81
|
+
|
|
82
|
+
Use `getOEISids` to get the most up-to-date list of available OEIS IDs.
|
|
83
|
+
|
|
84
|
+
```cmd
|
|
85
|
+
(mapFolding) C:\apps\mapFolding> getOEISids
|
|
86
|
+
|
|
87
|
+
Available OEIS sequences:
|
|
88
|
+
A001415: Number of ways of folding a 2 X n strip of stamps.
|
|
89
|
+
A001416: Number of ways of folding a 3 X n strip of stamps.
|
|
90
|
+
A001417: Number of ways of folding a 2 X 2 X ... X 2 n-dimensional map.
|
|
91
|
+
A001418: Number of ways of folding an n X n sheet of stamps.
|
|
92
|
+
A195646: Number of ways of folding a 3 X 3 X ... X 3 n-dimensional map.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. **Algorithm Zoo** 🦒
|
|
96
|
+
|
|
97
|
+
- **Lunnon’s 1971 Algorithm**: A painstakingly debugged version of [the original typo-riddled code](https://github.com/hunterhogan/mapFolding/blob/mapFolding/reference/foldings.txt)
|
|
98
|
+
- The /reference directory.
|
|
99
|
+
- **Numba-JIT Accelerated**: Up to 1000× faster than pure Python ([benchmarks](https://github.com/hunterhogan/mapFolding/blob/mapFolding/notes/Speed%20highlights.md))
|
|
100
|
+
|
|
101
|
+
### 3. **For Researchers** 🔬
|
|
102
|
+
|
|
103
|
+
- Change multiple minute settings, such as the bit width of the data types.
|
|
104
|
+
- Transform the algorithm using AST
|
|
105
|
+
- Create hyper-optimized modules to compute a specific map.
|
|
106
|
+
|
|
107
|
+
## Map-folding Video
|
|
108
|
+
|
|
109
|
+
~~This caused my neurosis:~~ I enjoyed the following video, which is what introduced me to map folding.
|
|
110
|
+
|
|
111
|
+
"How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/citations/Physics_for_the_Birds.bibtex) citation)
|
|
112
|
+
|
|
113
|
+
[](https://www.youtube.com/watch?v=sfH9uIY3ln4)
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## My recovery
|
|
118
|
+
|
|
119
|
+
[](https://HunterThinks.com/support)
|
|
120
|
+
[](https://www.youtube.com/@HunterHogan)
|
|
121
|
+
|
|
122
|
+
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
mapFolding/__init__.py,sha256=6HgDJ2_-lnTMVwXDhTs36MgQT6Ph0kYVvuvkeUP2u9w,1321
|
|
2
|
+
mapFolding/basecamp.py,sha256=v0VCF_Zgm_XBHcz4bqblsxfHwAxZKgenW6um77quWLk,3751
|
|
3
|
+
mapFolding/beDRY.py,sha256=XVtLraG9VnC4yG2HkaFwZRh2td4ZHMjTQvnbcD_W130,17133
|
|
4
|
+
mapFolding/oeis.py,sha256=rTMK4aQXxudmUGS-RkzikmSIPLdottVgJHKwY0atZqg,11120
|
|
5
|
+
mapFolding/theDao.py,sha256=ktYQSsF6pQrWA0bj6UVbvTsWgZP8mlEj1JKGQeqeFuI,12701
|
|
6
|
+
mapFolding/theSSOT.py,sha256=anbFOL2daNLcy9SuO_4EyFFlPsnKSpkvyO11Zf4QCoU,10174
|
|
7
|
+
mapFolding/theSSOTnumba.py,sha256=zGq2zlZZeuxiNSO2Fs_AqV6UhybJAJuDw-2lMVvDS2w,5133
|
|
8
|
+
mapFolding/reference/flattened.py,sha256=S6D9wiFTlbeoetEqaMLOcA-R22BHOzjqPRujffNxxUM,14875
|
|
9
|
+
mapFolding/reference/hunterNumba.py,sha256=jDS0ORHkIhcJ1rzA5hT49sZHKf3rgJOoGesUCcbKFFY,6054
|
|
10
|
+
mapFolding/reference/irvineJavaPort.py,sha256=7GvBU0tnS6wpFgkYad3465do9jBQW-2bYvbCYyABPHM,3341
|
|
11
|
+
mapFolding/reference/jax.py,sha256=7ji9YWia6Kof0cjcNdiS1GG1rMbC5SBjcyVr_07AeUk,13845
|
|
12
|
+
mapFolding/reference/lunnan.py,sha256=iAbJELfW6RKNMdPcBY9b6rGQ-z1zoRf-1XCurCRMOo8,3951
|
|
13
|
+
mapFolding/reference/lunnanNumpy.py,sha256=rwVP3WIDXimpAuaxhRIuBYU56nVDTKlfGiclw_FkgUU,3765
|
|
14
|
+
mapFolding/reference/lunnanWhile.py,sha256=uRrMT23jTJvoQDlD_FzeIQe_pfMXJG6_bRvs7uhC8z0,3271
|
|
15
|
+
mapFolding/reference/rotatedEntryPoint.py,sha256=USZY3n3zwhSE68ATscUuN66t1qShuEbMI790Gz9JFTw,9352
|
|
16
|
+
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=wpgay-uqPOBd64Z4Pg6tg40j7-4pzWHGMM6v0bnmjhE,6288
|
|
17
|
+
mapFolding/someAssemblyRequired/__init__.py,sha256=nqW7FcV65IS0tctqzzJ0MDex_z24es8OGYApzKcx1Ys,144
|
|
18
|
+
mapFolding/someAssemblyRequired/getLLVMforNoReason.py,sha256=nX8tghZClYt7zJd6RpZBXhE_h-CGRHOS17biqiEdf-o,855
|
|
19
|
+
mapFolding/someAssemblyRequired/makeJob.py,sha256=TZBJHgAEJ6c5XU4DHuyQgKOY-nf19Fvvoicyzn2huG0,2436
|
|
20
|
+
mapFolding/someAssemblyRequired/synthesizeModuleJAX.py,sha256=jatvtYhK5ZJK-YmCKATt7w3icFXXO79cZDAYVrU9bgA,1258
|
|
21
|
+
mapFolding/someAssemblyRequired/synthesizeNumba.py,sha256=949ZrnKy8rFsgZy8I5e82RMoFdCnVM9JML1l7lW-rOQ,17226
|
|
22
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py,sha256=9Xa-ftqDScsvhs-2zq5vsJ6LofCdaz1hMUbKPF49vPE,16080
|
|
23
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaJob.py,sha256=5q8ROqNHODy_WacVZq0vu4AHjqfwuS6mOmRZQWQ_XWE,8128
|
|
24
|
+
mapFolding/someAssemblyRequired/synthesizeNumbaModules.py,sha256=1V8tF-pjcmpFIywZuC-_i1EN3KsQAvI0SnIlzlXxDCs,3731
|
|
25
|
+
mapFolding/syntheticModules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
mapFolding/syntheticModules/numba_countInitialize.py,sha256=6-OlRBCEAKstJm-E0cs4VjPolYxiz9zSNvUGU6BRoTg,4274
|
|
27
|
+
mapFolding/syntheticModules/numba_countParallel.py,sha256=64HHCOPOnsw7bSuLClaWpeH6C8D1aksZSDj8b_xQJec,5517
|
|
28
|
+
mapFolding/syntheticModules/numba_countSequential.py,sha256=1_V4cIqiqKO2lvvc5IyNEX3ElMJs3ZiWB3HUU3sjdMI,3732
|
|
29
|
+
mapFolding/syntheticModules/numba_doTheNeedful.py,sha256=koAKvwn5gUO4gVoeXaA12qguZAYz_Tso42xAH9UVw8o,1368
|
|
30
|
+
tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
|
|
31
|
+
tests/conftest.py,sha256=gZTZlqW814yucdRhtSV6IsfAM67QgoW5rkdvlveyEFQ,11095
|
|
32
|
+
tests/test_oeis.py,sha256=72mL0MtBEYCh58t_qP8qvt6s9kV1fDk4Z9xVEapaapQ,5367
|
|
33
|
+
tests/test_other.py,sha256=fg7t8zW7zG26mw7d06E5cLfirhDrCjelMjwFAZ0C2KM,8433
|
|
34
|
+
tests/test_tasks.py,sha256=jjp-PzxrXMG9L7BBP_2NYLel5xlm-I552wLu3c_a4rw,2741
|
|
35
|
+
tests/test_types.py,sha256=58tmPG9WOeGGAQbdQK_h_7t4SnENnZugH4WXlI8-L-M,171
|
|
36
|
+
mapFolding-0.4.0.dist-info/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
37
|
+
mapFolding-0.4.0.dist-info/METADATA,sha256=H82VPfUNoVNwkEeSSDHSD6JzB05x1vEl7ADd1y-7Ht8,5241
|
|
38
|
+
mapFolding-0.4.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
39
|
+
mapFolding-0.4.0.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
40
|
+
mapFolding-0.4.0.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
41
|
+
mapFolding-0.4.0.dist-info/RECORD,,
|
tests/conftest.py
CHANGED
|
@@ -2,182 +2,292 @@
|
|
|
2
2
|
|
|
3
3
|
# TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
|
|
4
4
|
|
|
5
|
-
from tests.conftest_tmpRegistry import (
|
|
6
|
-
pathCacheTesting,
|
|
7
|
-
pathDataSamples,
|
|
8
|
-
pathFilenameFoldsTotalTesting,
|
|
9
|
-
pathTempTesting,
|
|
10
|
-
setupTeardownTestData,
|
|
11
|
-
)
|
|
12
|
-
from tests.conftest_uniformTests import (
|
|
13
|
-
uniformTestMessage,
|
|
14
|
-
standardizedEqualTo,
|
|
15
|
-
standardizedSystemExit,
|
|
16
|
-
)
|
|
17
5
|
from mapFolding import *
|
|
18
|
-
from mapFolding import basecamp
|
|
19
|
-
from mapFolding import getAlgorithmCallable, getDispatcherCallable
|
|
6
|
+
from mapFolding import basecamp, getAlgorithmCallable, getDispatcherCallable
|
|
20
7
|
from mapFolding.beDRY import *
|
|
21
|
-
from mapFolding.oeis import _getFilenameOEISbFile,
|
|
8
|
+
from mapFolding.oeis import _getFilenameOEISbFile, _getOEISidInformation, _getOEISidValues
|
|
22
9
|
from mapFolding.oeis import *
|
|
23
|
-
from Z0Z_tools.pytestForYourUse import PytestFor_defineConcurrencyLimit, PytestFor_intInnit, PytestFor_oopsieKwargsie
|
|
24
10
|
from typing import Any, Callable, ContextManager, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
|
|
11
|
+
from Z0Z_tools.pytestForYourUse import PytestFor_defineConcurrencyLimit, PytestFor_intInnit, PytestFor_oopsieKwargsie
|
|
25
12
|
import pathlib
|
|
26
13
|
import pytest
|
|
27
14
|
import random
|
|
15
|
+
import shutil
|
|
28
16
|
import unittest.mock
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
# SSOT for test data paths and filenames
|
|
20
|
+
pathDataSamples = pathlib.Path("tests/dataSamples")
|
|
21
|
+
# NOTE `tmp` is not a diminutive form of temporary: it signals a technical term. And "temp" is strongly disfavored.
|
|
22
|
+
pathTmpRoot = pathDataSamples / "tmp"
|
|
23
|
+
|
|
24
|
+
# The registrar maintains the register of temp files
|
|
25
|
+
registerOfTemporaryFilesystemObjects: Set[pathlib.Path] = set()
|
|
26
|
+
|
|
27
|
+
def registrarRecordsTmpObject(path: pathlib.Path) -> None:
|
|
28
|
+
"""The registrar adds a tmp file to the register."""
|
|
29
|
+
registerOfTemporaryFilesystemObjects.add(path)
|
|
30
|
+
|
|
31
|
+
def registrarDeletesTmpObjects() -> None:
|
|
32
|
+
"""The registrar cleans up tmp files in the register."""
|
|
33
|
+
for pathTmp in sorted(registerOfTemporaryFilesystemObjects, reverse=True):
|
|
34
|
+
try:
|
|
35
|
+
if pathTmp.is_file():
|
|
36
|
+
pathTmp.unlink(missing_ok=True)
|
|
37
|
+
elif pathTmp.is_dir():
|
|
38
|
+
shutil.rmtree(pathTmp, ignore_errors=True)
|
|
39
|
+
except Exception as ERRORmessage:
|
|
40
|
+
print(f"Warning: Failed to clean up {pathTmp}: {ERRORmessage}")
|
|
41
|
+
registerOfTemporaryFilesystemObjects.clear()
|
|
42
|
+
|
|
43
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
44
|
+
def setupTeardownTmpObjects() -> Generator[None, None, None]:
|
|
45
|
+
"""Auto-fixture to setup test data directories and cleanup after."""
|
|
46
|
+
pathDataSamples.mkdir(exist_ok=True)
|
|
47
|
+
pathTmpRoot.mkdir(exist_ok=True)
|
|
48
|
+
yield
|
|
49
|
+
registrarDeletesTmpObjects()
|
|
50
|
+
|
|
51
|
+
@pytest.fixture
|
|
52
|
+
def pathTmpTesting(request: pytest.FixtureRequest) -> pathlib.Path:
|
|
53
|
+
pathTmp = pathTmpRoot / str(uuid.uuid4().hex)
|
|
54
|
+
pathTmp.mkdir(parents=True, exist_ok=False)
|
|
55
|
+
|
|
56
|
+
registrarRecordsTmpObject(pathTmp)
|
|
57
|
+
return pathTmp
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def pathFilenameTmpTesting(request: pytest.FixtureRequest) -> pathlib.Path:
|
|
61
|
+
try:
|
|
62
|
+
extension = request.param
|
|
63
|
+
except AttributeError:
|
|
64
|
+
extension = ".txt"
|
|
65
|
+
|
|
66
|
+
uuidHex = uuid.uuid4().hex
|
|
67
|
+
subpath = uuidHex[0:-8]
|
|
68
|
+
filenameStem = uuidHex[-8:None]
|
|
69
|
+
|
|
70
|
+
pathFilenameTmp = pathlib.Path(pathTmpRoot, subpath, filenameStem + extension)
|
|
71
|
+
pathFilenameTmp.parent.mkdir(parents=True, exist_ok=False)
|
|
72
|
+
|
|
73
|
+
registrarRecordsTmpObject(pathFilenameTmp)
|
|
74
|
+
return pathFilenameTmp
|
|
75
|
+
|
|
76
|
+
@pytest.fixture
|
|
77
|
+
def pathCacheTesting(pathTmpTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
78
|
+
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
79
|
+
from mapFolding import oeis as there_must_be_a_better_way
|
|
80
|
+
pathCacheOriginal = there_must_be_a_better_way._pathCache
|
|
81
|
+
there_must_be_a_better_way._pathCache = pathTmpTesting
|
|
82
|
+
yield pathTmpTesting
|
|
83
|
+
there_must_be_a_better_way._pathCache = pathCacheOriginal
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def pathFilenameFoldsTotalTesting(pathTmpTesting: pathlib.Path) -> pathlib.Path:
|
|
87
|
+
return pathTmpTesting.joinpath("foldsTotalTest.txt")
|
|
29
88
|
|
|
30
89
|
def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
"""Returns a dictionary mapping dimension tuples to their known folding totals."""
|
|
91
|
+
dictionaryMapDimensionsToFoldsTotalKnown: Dict[Tuple[int, ...], int] = {}
|
|
92
|
+
|
|
93
|
+
for settings in settingsOEIS.values():
|
|
94
|
+
sequence = settings['valuesKnown']
|
|
95
|
+
|
|
96
|
+
for n, foldingsTotal in sequence.items():
|
|
97
|
+
dimensions = settings['getMapShape'](n)
|
|
98
|
+
dimensions.sort()
|
|
99
|
+
dictionaryMapDimensionsToFoldsTotalKnown[tuple(dimensions)] = foldingsTotal
|
|
100
|
+
|
|
101
|
+
# Are we in a place that has jobs?
|
|
102
|
+
pathJobDEFAULT = getPathJobRootDEFAULT()
|
|
103
|
+
if pathJobDEFAULT.exists():
|
|
104
|
+
# Are there foldsTotal files?
|
|
105
|
+
for pathFilenameFoldsTotal in pathJobDEFAULT.rglob('*.foldsTotal'):
|
|
106
|
+
if pathFilenameFoldsTotal.is_file():
|
|
107
|
+
try:
|
|
108
|
+
listDimensions = eval(pathFilenameFoldsTotal.stem)
|
|
109
|
+
except Exception:
|
|
110
|
+
continue
|
|
111
|
+
# Are the dimensions in the dictionary?
|
|
112
|
+
if isinstance(listDimensions, list) and all(isinstance(dimension, int) for dimension in listDimensions):
|
|
113
|
+
listDimensions.sort()
|
|
114
|
+
if tuple(listDimensions) in dictionaryMapDimensionsToFoldsTotalKnown:
|
|
115
|
+
continue
|
|
116
|
+
# Are the contents a reasonably large integer?
|
|
117
|
+
try:
|
|
118
|
+
foldsTotal = pathFilenameFoldsTotal.read_text()
|
|
119
|
+
except Exception:
|
|
120
|
+
continue
|
|
121
|
+
# Why did I sincerely believe this would only be three lines of code?
|
|
122
|
+
if foldsTotal.isdigit():
|
|
123
|
+
foldsTotalInteger = int(foldsTotal)
|
|
124
|
+
if foldsTotalInteger > 85109616 * 10**3:
|
|
125
|
+
# You made it this far, so fuck it: put it in the dictionary
|
|
126
|
+
dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotalInteger
|
|
127
|
+
dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotalInteger
|
|
128
|
+
# The sunk-costs fallacy claims another victim!
|
|
129
|
+
|
|
130
|
+
return dictionaryMapDimensionsToFoldsTotalKnown
|
|
72
131
|
|
|
73
132
|
"""
|
|
74
133
|
Section: Fixtures"""
|
|
75
134
|
|
|
76
135
|
@pytest.fixture(autouse=True)
|
|
77
136
|
def setupWarningsAsErrors() -> Generator[None, Any, None]:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
137
|
+
"""Convert all warnings to errors for all tests."""
|
|
138
|
+
import warnings
|
|
139
|
+
warnings.filterwarnings("error")
|
|
140
|
+
yield
|
|
141
|
+
warnings.resetwarnings()
|
|
83
142
|
|
|
84
143
|
@pytest.fixture
|
|
85
144
|
def foldsTotalKnown() -> Dict[Tuple[int,...], int]:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
145
|
+
"""Returns a dictionary mapping dimension tuples to their known folding totals.
|
|
146
|
+
NOTE I am not convinced this is the best way to do this.
|
|
147
|
+
Advantage: I call `makeDictionaryFoldsTotalKnown()` from modules other than test modules.
|
|
148
|
+
Preference: I _think_ I would prefer a SSOT function available to any module
|
|
149
|
+
similar to `foldsTotalKnown = getFoldsTotalKnown(listDimensions)`."""
|
|
150
|
+
return makeDictionaryFoldsTotalKnown()
|
|
92
151
|
|
|
93
152
|
@pytest.fixture
|
|
94
153
|
def listDimensionsTestCountFolds(oeisID: str) -> List[int]:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
154
|
+
"""For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
|
|
155
|
+
if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
|
|
156
|
+
while True:
|
|
157
|
+
n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
|
|
158
|
+
if n < 2:
|
|
159
|
+
continue
|
|
160
|
+
listDimensionsCandidate = settingsOEIS[oeisID]['getMapShape'](n)
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
return validateListDimensions(listDimensionsCandidate)
|
|
164
|
+
except (ValueError, NotImplementedError):
|
|
165
|
+
pass
|
|
107
166
|
|
|
108
167
|
@pytest.fixture
|
|
109
168
|
def listDimensionsTestFunctionality(oeisID_1random: str) -> List[int]:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
169
|
+
"""To test functionality, get one `listDimensions` from `valuesTestValidation` if
|
|
170
|
+
`validateListDimensions` approves. The algorithm can count the folds of the returned
|
|
171
|
+
`listDimensions` in a short enough time suitable for testing."""
|
|
172
|
+
while True:
|
|
173
|
+
n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
|
|
174
|
+
if n < 2:
|
|
175
|
+
continue
|
|
176
|
+
listDimensionsCandidate = settingsOEIS[oeisID_1random]['getMapShape'](n)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
return validateListDimensions(listDimensionsCandidate)
|
|
180
|
+
except (ValueError, NotImplementedError):
|
|
181
|
+
pass
|
|
123
182
|
|
|
124
183
|
@pytest.fixture
|
|
125
184
|
def listDimensionsTestParallelization(oeisID: str) -> List[int]:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
185
|
+
"""For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestParallelization`"""
|
|
186
|
+
n = random.choice(settingsOEIS[oeisID]['valuesTestParallelization'])
|
|
187
|
+
return settingsOEIS[oeisID]['getMapShape'](n)
|
|
129
188
|
|
|
130
189
|
@pytest.fixture
|
|
131
190
|
def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.AsyncMock, Any, None]:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
191
|
+
"""Mock time.perf_counter_ns for consistent benchmark timing."""
|
|
192
|
+
with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
|
|
193
|
+
mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
|
|
194
|
+
yield mockTimer
|
|
136
195
|
|
|
137
196
|
@pytest.fixture
|
|
138
197
|
def mockFoldingFunction() -> Callable[..., Callable[..., None]]:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
198
|
+
"""Creates a mock function that simulates _countFolds behavior."""
|
|
199
|
+
def make_mock(foldsValue: int, listDimensions: List[int]) -> Callable[..., None]:
|
|
200
|
+
mock_array = makeDataContainer(2)
|
|
201
|
+
mock_array[0] = foldsValue
|
|
202
|
+
mock_array[-1] = getLeavesTotal(listDimensions)
|
|
144
203
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
204
|
+
def mock_countFolds(**keywordArguments: Any) -> None:
|
|
205
|
+
keywordArguments['foldGroups'][:] = mock_array
|
|
206
|
+
return None
|
|
148
207
|
|
|
149
|
-
|
|
150
|
-
|
|
208
|
+
return mock_countFolds
|
|
209
|
+
return make_mock
|
|
151
210
|
|
|
152
211
|
@pytest.fixture
|
|
153
212
|
def mockDispatcher() -> Callable[[Any], ContextManager[Any]]:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
213
|
+
"""Context manager for mocking dispatcher callable."""
|
|
214
|
+
def wrapper(mockFunction: Any) -> ContextManager[Any]:
|
|
215
|
+
dispatcherCallable = getDispatcherCallable()
|
|
216
|
+
return unittest.mock.patch(
|
|
217
|
+
f"{dispatcherCallable.__module__}.{dispatcherCallable.__name__}",
|
|
218
|
+
side_effect=mockFunction
|
|
219
|
+
)
|
|
220
|
+
return wrapper
|
|
162
221
|
|
|
163
222
|
@pytest.fixture(params=oeisIDsImplemented)
|
|
164
223
|
def oeisID(request: pytest.FixtureRequest) -> Any:
|
|
165
|
-
|
|
224
|
+
return request.param
|
|
166
225
|
|
|
167
226
|
@pytest.fixture
|
|
168
227
|
def oeisID_1random() -> str:
|
|
169
|
-
|
|
170
|
-
|
|
228
|
+
"""Return one random valid OEIS ID."""
|
|
229
|
+
return random.choice(oeisIDsImplemented)
|
|
171
230
|
|
|
172
231
|
@pytest.fixture
|
|
173
232
|
def useAlgorithmDirectly() -> Generator[None, Any, None]:
|
|
174
|
-
|
|
175
|
-
|
|
233
|
+
"""Temporarily patches getDispatcherCallable to return the algorithm source directly."""
|
|
234
|
+
original_dispatcher = basecamp.getDispatcherCallable
|
|
235
|
+
|
|
236
|
+
# Patch the function at module level
|
|
237
|
+
basecamp.getDispatcherCallable = getAlgorithmCallable
|
|
238
|
+
|
|
239
|
+
yield
|
|
240
|
+
|
|
241
|
+
# Restore original function
|
|
242
|
+
basecamp.getDispatcherCallable = original_dispatcher
|
|
243
|
+
|
|
244
|
+
def uniformTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
245
|
+
"""Format assertion message for any test comparison."""
|
|
246
|
+
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
247
|
+
f"Expected: {expected}\n"
|
|
248
|
+
f"Got: {actual}")
|
|
249
|
+
|
|
250
|
+
def standardizedEqualTo(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
|
|
251
|
+
"""Template for tests expecting an error."""
|
|
252
|
+
if type(expected) is Type[Exception]:
|
|
253
|
+
messageExpected = expected.__name__
|
|
254
|
+
else:
|
|
255
|
+
messageExpected = expected
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
messageActual = actual = functionTarget(*arguments)
|
|
259
|
+
except Exception as actualError:
|
|
260
|
+
messageActual = type(actualError).__name__
|
|
261
|
+
actual = type(actualError)
|
|
262
|
+
|
|
263
|
+
assert actual == expected, uniformTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
|
|
264
|
+
|
|
265
|
+
def standardizedSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: Callable, *arguments: Any) -> None:
|
|
266
|
+
"""Template for tests expecting SystemExit.
|
|
176
267
|
|
|
177
|
-
|
|
178
|
-
|
|
268
|
+
Parameters
|
|
269
|
+
expected: Exit code expectation:
|
|
270
|
+
- "error": any non-zero exit code
|
|
271
|
+
- "nonError": specifically zero exit code
|
|
272
|
+
- int: exact exit code match
|
|
273
|
+
- Sequence[int]: exit code must be one of these values
|
|
274
|
+
functionTarget: The function to test
|
|
275
|
+
arguments: Arguments to pass to the function
|
|
276
|
+
"""
|
|
277
|
+
with pytest.raises(SystemExit) as exitInfo:
|
|
278
|
+
functionTarget(*arguments)
|
|
179
279
|
|
|
180
|
-
|
|
280
|
+
exitCode = exitInfo.value.code
|
|
181
281
|
|
|
182
|
-
|
|
183
|
-
|
|
282
|
+
if expected == "error":
|
|
283
|
+
assert exitCode != 0, \
|
|
284
|
+
f"Expected error exit (non-zero) but got code {exitCode}"
|
|
285
|
+
elif expected == "nonError":
|
|
286
|
+
assert exitCode == 0, \
|
|
287
|
+
f"Expected non-error exit (0) but got code {exitCode}"
|
|
288
|
+
elif isinstance(expected, (list, tuple)):
|
|
289
|
+
assert exitCode in expected, \
|
|
290
|
+
f"Expected exit code to be one of {expected} but got {exitCode}"
|
|
291
|
+
else:
|
|
292
|
+
assert exitCode == expected, \
|
|
293
|
+
f"Expected exit code {expected} but got {exitCode}"
|