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.
Files changed (44) hide show
  1. mapFolding/__init__.py +44 -32
  2. mapFolding/basecamp.py +50 -50
  3. mapFolding/beDRY.py +336 -336
  4. mapFolding/oeis.py +262 -262
  5. mapFolding/reference/flattened.py +294 -293
  6. mapFolding/reference/hunterNumba.py +126 -126
  7. mapFolding/reference/irvineJavaPort.py +99 -99
  8. mapFolding/reference/jax.py +153 -153
  9. mapFolding/reference/lunnan.py +148 -148
  10. mapFolding/reference/lunnanNumpy.py +115 -115
  11. mapFolding/reference/lunnanWhile.py +114 -114
  12. mapFolding/reference/rotatedEntryPoint.py +183 -183
  13. mapFolding/reference/total_countPlus1vsPlusN.py +203 -203
  14. mapFolding/someAssemblyRequired/__init__.py +2 -1
  15. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +12 -12
  16. mapFolding/someAssemblyRequired/makeJob.py +48 -48
  17. mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +17 -17
  18. mapFolding/someAssemblyRequired/synthesizeNumba.py +345 -803
  19. mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +371 -0
  20. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +150 -0
  21. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +75 -0
  22. mapFolding/syntheticModules/__init__.py +0 -0
  23. mapFolding/syntheticModules/numba_countInitialize.py +2 -2
  24. mapFolding/syntheticModules/numba_countParallel.py +3 -3
  25. mapFolding/syntheticModules/numba_countSequential.py +28 -28
  26. mapFolding/syntheticModules/numba_doTheNeedful.py +6 -6
  27. mapFolding/theDao.py +168 -169
  28. mapFolding/theSSOT.py +190 -162
  29. mapFolding/theSSOTnumba.py +91 -75
  30. mapFolding-0.4.0.dist-info/METADATA +122 -0
  31. mapFolding-0.4.0.dist-info/RECORD +41 -0
  32. tests/conftest.py +238 -128
  33. tests/test_oeis.py +80 -80
  34. tests/test_other.py +137 -224
  35. tests/test_tasks.py +21 -21
  36. tests/test_types.py +2 -2
  37. mapFolding-0.3.11.dist-info/METADATA +0 -155
  38. mapFolding-0.3.11.dist-info/RECORD +0 -39
  39. tests/conftest_tmpRegistry.py +0 -62
  40. tests/conftest_uniformTests.py +0 -53
  41. {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/LICENSE +0 -0
  42. {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/WHEEL +0 -0
  43. {mapFolding-0.3.11.dist-info → mapFolding-0.4.0.dist-info}/entry_points.txt +0 -0
  44. {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
+ [![pip install mapFolding](https://img.shields.io/badge/pip%20install-mapFolding-gray.svg?colorB=3b434b)](https://pypi.org/project/mapFolding/)
44
+ [![Static Badge](https://img.shields.io/badge/stinkin'%20badges-don't%20need-b98e5e)](https://youtu.be/g6f_miE91mk&t=4)
45
+ [![Python Tests](https://github.com/hunterhogan/mapFolding/actions/workflows/pythonTests.yml/badge.svg)](https://github.com/hunterhogan/mapFolding/actions/workflows/pythonTests.yml)
46
+ ![Static Badge](https://img.shields.io/badge/issues-I%20have%20them-brightgreen)
47
+ [![License: CC-BY-NC-4.0](https://img.shields.io/badge/License-CC_BY--NC_4.0-3b434b)](https://creativecommons.org/licenses/by-nc/4.0/)
48
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/mapFolding)
49
+ ![GitHub repo size](https://img.shields.io/github/repo-size/hunterhogan/mapFolding)
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
+ [![How Many Ways Can You Fold a Map?](https://i.ytimg.com/vi/sfH9uIY3ln4/hq720.jpg)](https://www.youtube.com/watch?v=sfH9uIY3ln4)
114
+
115
+ ---
116
+
117
+ ## My recovery
118
+
119
+ [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
120
+ [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
121
+
122
+ [![CC-BY-NC-4.0](https://github.com/hunterhogan/mapFolding/blob/main/CC-BY-NC-4.0.svg)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -0,0 +1,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, _getOEISidValues, _getOEISidInformation
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
- """Returns a dictionary mapping dimension tuples to their known folding totals."""
32
- dictionaryMapDimensionsToFoldsTotalKnown: Dict[Tuple[int, ...], int] = {}
33
-
34
- for settings in settingsOEIS.values():
35
- sequence = settings['valuesKnown']
36
-
37
- for n, foldingsTotal in sequence.items():
38
- dimensions = settings['getMapShape'](n)
39
- dimensions.sort()
40
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(dimensions)] = foldingsTotal
41
-
42
- # Are we in a place that has jobs?
43
- pathJobDEFAULT = getPathJobRootDEFAULT()
44
- if pathJobDEFAULT.exists():
45
- # Are there foldsTotal files?
46
- for pathFilenameFoldsTotal in pathJobDEFAULT.rglob('*.foldsTotal'):
47
- if pathFilenameFoldsTotal.is_file():
48
- try:
49
- listDimensions = eval(pathFilenameFoldsTotal.stem)
50
- except Exception:
51
- continue
52
- # Are the dimensions in the dictionary?
53
- if isinstance(listDimensions, list) and all(isinstance(dimension, int) for dimension in listDimensions):
54
- listDimensions.sort()
55
- if tuple(listDimensions) in dictionaryMapDimensionsToFoldsTotalKnown:
56
- continue
57
- # Are the contents a reasonably large integer?
58
- try:
59
- foldsTotal = pathFilenameFoldsTotal.read_text()
60
- except Exception:
61
- continue
62
- # Why did I sincerely believe this would only be three lines of code?
63
- if foldsTotal.isdigit():
64
- foldsTotalInteger = int(foldsTotal)
65
- if foldsTotalInteger > 85109616 * 10**3:
66
- # You made it this far, so fuck it: put it in the dictionary
67
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotalInteger
68
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotalInteger
69
- # The sunk-costs fallacy claims another victim!
70
-
71
- return dictionaryMapDimensionsToFoldsTotalKnown
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
- """Convert all warnings to errors for all tests."""
79
- import warnings
80
- warnings.filterwarnings("error")
81
- yield
82
- warnings.resetwarnings()
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
- """Returns a dictionary mapping dimension tuples to their known folding totals.
87
- NOTE I am not convinced this is the best way to do this.
88
- Advantage: I call `makeDictionaryFoldsTotalKnown()` from modules other than test modules.
89
- Preference: I _think_ I would prefer a SSOT function available to any module
90
- similar to `foldsTotalKnown = getFoldsTotalKnown(listDimensions)`."""
91
- return makeDictionaryFoldsTotalKnown()
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
- """For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
96
- if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
97
- while True:
98
- n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
99
- if n < 2:
100
- continue
101
- listDimensionsCandidate = settingsOEIS[oeisID]['getMapShape'](n)
102
-
103
- try:
104
- return validateListDimensions(listDimensionsCandidate)
105
- except (ValueError, NotImplementedError):
106
- pass
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
- """To test functionality, get one `listDimensions` from `valuesTestValidation` if
111
- `validateListDimensions` approves. The algorithm can count the folds of the returned
112
- `listDimensions` in a short enough time suitable for testing."""
113
- while True:
114
- n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
115
- if n < 2:
116
- continue
117
- listDimensionsCandidate = settingsOEIS[oeisID_1random]['getMapShape'](n)
118
-
119
- try:
120
- return validateListDimensions(listDimensionsCandidate)
121
- except (ValueError, NotImplementedError):
122
- pass
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
- """For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestParallelization`"""
127
- n = random.choice(settingsOEIS[oeisID]['valuesTestParallelization'])
128
- return settingsOEIS[oeisID]['getMapShape'](n)
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
- """Mock time.perf_counter_ns for consistent benchmark timing."""
133
- with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
134
- mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
135
- yield mockTimer
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
- """Creates a mock function that simulates _countFolds behavior."""
140
- def make_mock(foldsValue: int, listDimensions: List[int]) -> Callable[..., None]:
141
- mock_array = makeDataContainer(2)
142
- mock_array[0] = foldsValue
143
- mock_array[-1] = getLeavesTotal(listDimensions)
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
- def mock_countFolds(**keywordArguments: Any) -> None:
146
- keywordArguments['foldGroups'][:] = mock_array
147
- return None
204
+ def mock_countFolds(**keywordArguments: Any) -> None:
205
+ keywordArguments['foldGroups'][:] = mock_array
206
+ return None
148
207
 
149
- return mock_countFolds
150
- return make_mock
208
+ return mock_countFolds
209
+ return make_mock
151
210
 
152
211
  @pytest.fixture
153
212
  def mockDispatcher() -> Callable[[Any], ContextManager[Any]]:
154
- """Context manager for mocking dispatcher callable."""
155
- def wrapper(mockFunction: Any) -> ContextManager[Any]:
156
- dispatcherCallable = getDispatcherCallable()
157
- return unittest.mock.patch(
158
- f"{dispatcherCallable.__module__}.{dispatcherCallable.__name__}",
159
- side_effect=mockFunction
160
- )
161
- return wrapper
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
- return request.param
224
+ return request.param
166
225
 
167
226
  @pytest.fixture
168
227
  def oeisID_1random() -> str:
169
- """Return one random valid OEIS ID."""
170
- return random.choice(oeisIDsImplemented)
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
- """Temporarily patches getDispatcherCallable to return the algorithm source directly."""
175
- original_dispatcher = basecamp.getDispatcherCallable
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
- # Patch the function at module level
178
- basecamp.getDispatcherCallable = getAlgorithmCallable
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
- yield
280
+ exitCode = exitInfo.value.code
181
281
 
182
- # Restore original function
183
- basecamp.getDispatcherCallable = original_dispatcher
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}"