mapFolding 0.2.6__py3-none-any.whl → 0.3.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-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/METADATA +23 -7
- mapFolding-0.3.0.dist-info/RECORD +20 -0
- mapFolding-0.3.0.dist-info/top_level.txt +3 -0
- someAssemblyRequired/__init__.py +3 -0
- someAssemblyRequired/countInitialize.py +45 -0
- someAssemblyRequired/countParallel.py +52 -0
- someAssemblyRequired/countSequential.py +59 -0
- mapFolding/someAssemblyRequired/inlineAfunction.py → someAssemblyRequired/synthesizeModules.py +76 -41
- mapFolding/__init__.py +0 -12
- mapFolding/babbage.py +0 -35
- mapFolding/beDRY.py +0 -319
- mapFolding/importSelector.py +0 -7
- mapFolding/lovelace.py +0 -213
- mapFolding/oeis.py +0 -323
- mapFolding/someAssemblyRequired/jobsAndTasks.py +0 -47
- mapFolding/someAssemblyRequired/makeNuitkaSource.py +0 -99
- mapFolding/someAssemblyRequired/makeNumbaJob.py +0 -144
- mapFolding/startHere.py +0 -50
- mapFolding/theSSOT.py +0 -76
- mapFolding-0.2.6.dist-info/RECORD +0 -33
- mapFolding-0.2.6.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -1
- tests/conftest.py +0 -343
- tests/pythons_idiotic_namespace.py +0 -1
- tests/test_oeis.py +0 -194
- tests/test_other.py +0 -282
- tests/test_tasks.py +0 -31
- {mapFolding/benchmarks → benchmarks}/benchmarking.py +0 -0
- {mapFolding-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/WHEEL +0 -0
- {mapFolding-0.2.6.dist-info → mapFolding-0.3.0.dist-info}/entry_points.txt +0 -0
- {mapFolding/reference → reference}/flattened.py +0 -0
- {mapFolding/reference → reference}/hunterNumba.py +0 -0
- {mapFolding/reference → reference}/irvineJavaPort.py +0 -0
- {mapFolding/reference → reference}/jax.py +0 -0
- {mapFolding/reference → reference}/lunnan.py +0 -0
- {mapFolding/reference → reference}/lunnanNumpy.py +0 -0
- {mapFolding/reference → reference}/lunnanWhile.py +0 -0
- {mapFolding/reference → reference}/rotatedEntryPoint.py +0 -0
- {mapFolding/reference → reference}/total_countPlus1vsPlusN.py +0 -0
mapFolding/theSSOT.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from typing import Any, Tuple, TypedDict
|
|
2
|
-
import enum
|
|
3
|
-
import numpy
|
|
4
|
-
import numpy.typing
|
|
5
|
-
import pathlib
|
|
6
|
-
import sys
|
|
7
|
-
|
|
8
|
-
datatypeModule = 'numpy'
|
|
9
|
-
|
|
10
|
-
datatypeLarge = 'int64'
|
|
11
|
-
datatypeDefault = datatypeLarge
|
|
12
|
-
datatypeSmall = datatypeDefault
|
|
13
|
-
|
|
14
|
-
make_dtype = lambda _datatype: eval(f"{datatypeModule}.{_datatype}")
|
|
15
|
-
|
|
16
|
-
dtypeLarge = make_dtype(datatypeLarge)
|
|
17
|
-
dtypeDefault = make_dtype(datatypeDefault)
|
|
18
|
-
dtypeSmall = make_dtype(datatypeSmall)
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
_pathModule = pathlib.Path(__file__).parent
|
|
22
|
-
except NameError:
|
|
23
|
-
_pathModule = pathlib.Path.cwd()
|
|
24
|
-
|
|
25
|
-
pathJobDEFAULT = _pathModule / "jobs"
|
|
26
|
-
|
|
27
|
-
if 'google.colab' in sys.modules:
|
|
28
|
-
pathJobDEFAULT = pathlib.Path("/content/drive/MyDrive") / "jobs"
|
|
29
|
-
|
|
30
|
-
@enum.verify(enum.CONTINUOUS, enum.UNIQUE) if sys.version_info >= (3, 11) else lambda x: x
|
|
31
|
-
class EnumIndices(enum.IntEnum):
|
|
32
|
-
"""Base class for index enums."""
|
|
33
|
-
@staticmethod
|
|
34
|
-
def _generate_next_value_(name, start, count, last_values):
|
|
35
|
-
"""0-indexed."""
|
|
36
|
-
return count
|
|
37
|
-
|
|
38
|
-
def __index__(self) -> int:
|
|
39
|
-
"""Adapt enum to the ultra-rare event of indexing a NumPy 'ndarray', which is not the
|
|
40
|
-
same as `array.array`. See NumPy.org; I think it will be very popular someday."""
|
|
41
|
-
return self.value
|
|
42
|
-
|
|
43
|
-
class indexMy(EnumIndices):
|
|
44
|
-
"""Indices for dynamic values."""
|
|
45
|
-
dimensionsTotal = enum.auto() # connectionGraph.shape[0]
|
|
46
|
-
dimensionsUnconstrained = enum.auto()
|
|
47
|
-
gap1ndex = enum.auto()
|
|
48
|
-
gap1ndexCeiling = enum.auto()
|
|
49
|
-
indexDimension = enum.auto()
|
|
50
|
-
indexLeaf = enum.auto()
|
|
51
|
-
indexMiniGap = enum.auto()
|
|
52
|
-
leaf1ndex = enum.auto()
|
|
53
|
-
leafConnectee = enum.auto()
|
|
54
|
-
taskDivisions = enum.auto()
|
|
55
|
-
taskIndex = enum.auto()
|
|
56
|
-
|
|
57
|
-
# class indexThe(EnumIndices):
|
|
58
|
-
# """Indices for static values."""
|
|
59
|
-
# dimensionsTotal = enum.auto() # connectionGraph.shape[0]
|
|
60
|
-
# taskDivisions = enum.auto()
|
|
61
|
-
|
|
62
|
-
class indexTrack(EnumIndices):
|
|
63
|
-
"""Indices for state tracking array."""
|
|
64
|
-
leafAbove = enum.auto()
|
|
65
|
-
leafBelow = enum.auto()
|
|
66
|
-
countDimensionsGapped = enum.auto()
|
|
67
|
-
gapRangeStart = enum.auto()
|
|
68
|
-
|
|
69
|
-
class computationState(TypedDict):
|
|
70
|
-
connectionGraph: numpy.typing.NDArray[numpy.integer[Any]]
|
|
71
|
-
foldGroups: numpy.typing.NDArray[numpy.integer[Any]]
|
|
72
|
-
gapsWhere: numpy.typing.NDArray[numpy.integer[Any]]
|
|
73
|
-
mapShape: Tuple[int, ...]
|
|
74
|
-
my: numpy.typing.NDArray[numpy.integer[Any]]
|
|
75
|
-
# the: numpy.typing.NDArray[numpy.integer[Any]]
|
|
76
|
-
track: numpy.typing.NDArray[numpy.integer[Any]]
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
mapFolding/__init__.py,sha256=yZ_rcMMCco346M62nKzp90GPp9OV1UkkWSxKzP3ISPA,380
|
|
2
|
-
mapFolding/babbage.py,sha256=IfUoov6WYf9ExpnUoOMgw17GmaLueWseANelnTjomUk,2144
|
|
3
|
-
mapFolding/beDRY.py,sha256=SLN7Rmo8pZ1tXoV4ZDeXDrHoOpoRMxBiq1F2E_ngtIw,15496
|
|
4
|
-
mapFolding/importSelector.py,sha256=uVdA2oUoo11Cq1QXfRslgvzdrkTUIWXzqN6-eADK1bA,373
|
|
5
|
-
mapFolding/lovelace.py,sha256=1HIkzuI3SQ6HCAVcGKpHt6Q9IFneYddGa1V8xkwTy7Y,14967
|
|
6
|
-
mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
|
|
7
|
-
mapFolding/startHere.py,sha256=7VShI9OHHb-CqSkQ4XHKSp2zGpbN0hue3xDD-HHpyuY,3922
|
|
8
|
-
mapFolding/theSSOT.py,sha256=v31S2Z4A_x5si5UZHoVxhvVpMLYdknN79FQrFwZO0q4,2414
|
|
9
|
-
mapFolding/benchmarks/benchmarking.py,sha256=HD_0NSvuabblg94ftDre6LFnXShTe8MYj3hIodW-zV0,3076
|
|
10
|
-
mapFolding/reference/flattened.py,sha256=X9nvRzg7YDcpCtSDTL4YiidjshlX9rg2e6JVCY6i2u0,16547
|
|
11
|
-
mapFolding/reference/hunterNumba.py,sha256=0giUyqAFzP-XKcq3Kz8wIWCK0BVFhjABVJ1s-w4Jhu0,7109
|
|
12
|
-
mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
|
|
13
|
-
mapFolding/reference/jax.py,sha256=bB34dGdi3VSz4cRFbmCPn_erAmQ3FyrSED8uJ7CsES0,14961
|
|
14
|
-
mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
|
|
15
|
-
mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
|
|
16
|
-
mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
|
|
17
|
-
mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
|
|
18
|
-
mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
|
|
19
|
-
mapFolding/someAssemblyRequired/inlineAfunction.py,sha256=JrmLc2w6MciC8nsxpIzea5rqQPxZi97e60irBxkHzro,6201
|
|
20
|
-
mapFolding/someAssemblyRequired/jobsAndTasks.py,sha256=PR1waaYHMhUzNjRm6cgVg_AukLtKzLb2_NSOIn9APDY,2238
|
|
21
|
-
mapFolding/someAssemblyRequired/makeNuitkaSource.py,sha256=jTK34OWzm6OsgFPd2mHwETxFo2X83io0M4YiEHRgk3U,3262
|
|
22
|
-
mapFolding/someAssemblyRequired/makeNumbaJob.py,sha256=i9vYBfqtZdZp1sPEQ1McFao0pq4s_Ppo4VSkS4SFozU,5823
|
|
23
|
-
tests/__init__.py,sha256=eg9smg-6VblOr0kisM40CpGnuDtU2JgEEWGDTFVOlW8,57
|
|
24
|
-
tests/conftest.py,sha256=Xj-R4yTq5h8lVVKhPSh3UooI9rXzfjZhYw8sP-oYDcc,13588
|
|
25
|
-
tests/pythons_idiotic_namespace.py,sha256=oOLDBergQqqhGuRpsXUnFD-R_6AlJipNKYHw-kk_OKw,33
|
|
26
|
-
tests/test_oeis.py,sha256=vxnwO-cSR68htkyMh9QMVv-lvxBo6qlwPg1Rbx4JylY,7963
|
|
27
|
-
tests/test_other.py,sha256=98W-xsNQQuxYd4OiaRWUG7rcr4E5Q3SLCIMOjxr7FEo,12820
|
|
28
|
-
tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
|
|
29
|
-
mapFolding-0.2.6.dist-info/METADATA,sha256=8oni55uKek5pb5Hw8Mu3ISUJHUTYV3w0HYm2eLk0eGA,6652
|
|
30
|
-
mapFolding-0.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
31
|
-
mapFolding-0.2.6.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
|
|
32
|
-
mapFolding-0.2.6.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
|
|
33
|
-
mapFolding-0.2.6.dist-info/RECORD,,
|
tests/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from tests.conftest import makeDictionaryFoldsTotalKnown
|
tests/conftest.py
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
"""SSOT for Pytest"""
|
|
2
|
-
|
|
3
|
-
# TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
|
|
4
|
-
|
|
5
|
-
from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
|
|
6
|
-
import pathlib
|
|
7
|
-
import pytest
|
|
8
|
-
import random
|
|
9
|
-
import shutil
|
|
10
|
-
import unittest.mock
|
|
11
|
-
import uuid
|
|
12
|
-
from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
|
|
13
|
-
from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
|
|
14
|
-
from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
|
|
15
|
-
from mapFolding import countFolds, pathJobDEFAULT, indexMy, indexTrack, saveFoldsTotal
|
|
16
|
-
from mapFolding import defineConcurrencyLimit, intInnit, oopsieKwargsie, outfitCountFolds
|
|
17
|
-
from mapFolding import oeisIDfor_n, getOEISids, clearOEIScache, getFilenameFoldsTotal
|
|
18
|
-
from mapFolding.beDRY import getLeavesTotal, parseDimensions, validateListDimensions
|
|
19
|
-
from mapFolding.beDRY import getTaskDivisions, makeConnectionGraph, setCPUlimit
|
|
20
|
-
from mapFolding.beDRY import makeDataContainer
|
|
21
|
-
from mapFolding.oeis import OEIS_for_n
|
|
22
|
-
from mapFolding.oeis import _getFilenameOEISbFile
|
|
23
|
-
from mapFolding.oeis import _getOEISidValues
|
|
24
|
-
from mapFolding.oeis import _parseBFileOEIS
|
|
25
|
-
from mapFolding.oeis import _validateOEISid
|
|
26
|
-
from mapFolding.oeis import oeisIDsImplemented
|
|
27
|
-
from mapFolding.oeis import settingsOEIS
|
|
28
|
-
|
|
29
|
-
__all__ = [
|
|
30
|
-
'OEIS_for_n',
|
|
31
|
-
'_getFilenameOEISbFile',
|
|
32
|
-
'_getOEISidValues',
|
|
33
|
-
'_parseBFileOEIS',
|
|
34
|
-
'_validateOEISid',
|
|
35
|
-
'clearOEIScache',
|
|
36
|
-
'countFolds',
|
|
37
|
-
'defineConcurrencyLimit',
|
|
38
|
-
'expectSystemExit',
|
|
39
|
-
'getFilenameFoldsTotal',
|
|
40
|
-
'getLeavesTotal',
|
|
41
|
-
'getOEISids',
|
|
42
|
-
'getTaskDivisions',
|
|
43
|
-
'intInnit',
|
|
44
|
-
'makeConnectionGraph',
|
|
45
|
-
'makeDataContainer',
|
|
46
|
-
'makeTestSuiteConcurrencyLimit',
|
|
47
|
-
'makeTestSuiteIntInnit',
|
|
48
|
-
'makeTestSuiteOopsieKwargsie',
|
|
49
|
-
'oeisIDfor_n',
|
|
50
|
-
'oeisIDsImplemented',
|
|
51
|
-
'oopsieKwargsie',
|
|
52
|
-
'outfitCountFolds',
|
|
53
|
-
'parseDimensions',
|
|
54
|
-
'saveFoldsTotal',
|
|
55
|
-
'setCPUlimit',
|
|
56
|
-
'settingsOEIS',
|
|
57
|
-
'standardCacheTest',
|
|
58
|
-
'standardComparison',
|
|
59
|
-
'validateListDimensions',
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
|
|
63
|
-
"""Returns a dictionary mapping dimension tuples to their known folding totals."""
|
|
64
|
-
dictionaryMapDimensionsToFoldsTotalKnown = {}
|
|
65
|
-
|
|
66
|
-
for settings in settingsOEIS.values():
|
|
67
|
-
sequence = settings['valuesKnown']
|
|
68
|
-
|
|
69
|
-
for n, foldingsTotal in sequence.items():
|
|
70
|
-
dimensions = settings['getDimensions'](n)
|
|
71
|
-
dimensions.sort()
|
|
72
|
-
dictionaryMapDimensionsToFoldsTotalKnown[tuple(dimensions)] = foldingsTotal
|
|
73
|
-
|
|
74
|
-
# Are we in a place that has jobs?
|
|
75
|
-
if pathJobDEFAULT.exists():
|
|
76
|
-
# Are there foldsTotal files?
|
|
77
|
-
for pathFilenameFoldsTotal in pathJobDEFAULT.rglob('*.foldsTotal'):
|
|
78
|
-
if pathFilenameFoldsTotal.is_file():
|
|
79
|
-
try:
|
|
80
|
-
listDimensions = eval(pathFilenameFoldsTotal.stem)
|
|
81
|
-
except Exception:
|
|
82
|
-
continue
|
|
83
|
-
# Are the dimensions in the dictionary?
|
|
84
|
-
if isinstance(listDimensions, list) and all(isinstance(dimension, int) for dimension in listDimensions):
|
|
85
|
-
listDimensions.sort()
|
|
86
|
-
if tuple(listDimensions) in dictionaryMapDimensionsToFoldsTotalKnown:
|
|
87
|
-
continue
|
|
88
|
-
# Are the contents a reasonably large integer?
|
|
89
|
-
try:
|
|
90
|
-
foldsTotal = pathFilenameFoldsTotal.read_text()
|
|
91
|
-
except Exception:
|
|
92
|
-
continue
|
|
93
|
-
# Why did I sincerely believe this would only be three lines of code?
|
|
94
|
-
if foldsTotal.isdigit() and int(foldsTotal) > 85109616 * 10**3:
|
|
95
|
-
foldsTotal = int(foldsTotal)
|
|
96
|
-
# You made it this far, so fuck it: put it in the dictionary
|
|
97
|
-
dictionaryMapDimensionsToFoldsTotalKnown[tuple(listDimensions)] = foldsTotal
|
|
98
|
-
# The sunk-costs fallacy claims another victim!
|
|
99
|
-
|
|
100
|
-
return dictionaryMapDimensionsToFoldsTotalKnown
|
|
101
|
-
|
|
102
|
-
"""
|
|
103
|
-
Section: temporary paths and pathFilenames"""
|
|
104
|
-
|
|
105
|
-
# SSOT for test data paths
|
|
106
|
-
pathDataSamples = pathlib.Path("tests/dataSamples")
|
|
107
|
-
pathTempRoot = pathDataSamples / "tmp"
|
|
108
|
-
|
|
109
|
-
# The registrar maintains the register of temp files
|
|
110
|
-
registerOfTempFiles: Set[pathlib.Path] = set()
|
|
111
|
-
|
|
112
|
-
def addTempFileToRegister(path: pathlib.Path) -> None:
|
|
113
|
-
"""The registrar adds a temp file to the register."""
|
|
114
|
-
registerOfTempFiles.add(path)
|
|
115
|
-
|
|
116
|
-
def cleanupTempFileRegister() -> None:
|
|
117
|
-
"""The registrar cleans up temp files in the register."""
|
|
118
|
-
for pathTemp in sorted(registerOfTempFiles, reverse=True):
|
|
119
|
-
try:
|
|
120
|
-
if pathTemp.is_file():
|
|
121
|
-
pathTemp.unlink(missing_ok=True)
|
|
122
|
-
elif pathTemp.is_dir():
|
|
123
|
-
shutil.rmtree(pathTemp, ignore_errors=True)
|
|
124
|
-
except Exception as ERRORmessage:
|
|
125
|
-
print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
|
|
126
|
-
registerOfTempFiles.clear()
|
|
127
|
-
|
|
128
|
-
@pytest.fixture(scope="session", autouse=True)
|
|
129
|
-
def setupTeardownTestData() -> Generator[None, None, None]:
|
|
130
|
-
"""Auto-fixture to setup test data directories and cleanup after."""
|
|
131
|
-
pathDataSamples.mkdir(exist_ok=True)
|
|
132
|
-
pathTempRoot.mkdir(exist_ok=True)
|
|
133
|
-
yield
|
|
134
|
-
cleanupTempFileRegister()
|
|
135
|
-
|
|
136
|
-
@pytest.fixture(autouse=True)
|
|
137
|
-
def setupWarningsAsErrors():
|
|
138
|
-
"""Convert all warnings to errors for all tests."""
|
|
139
|
-
import warnings
|
|
140
|
-
warnings.filterwarnings("error")
|
|
141
|
-
yield
|
|
142
|
-
warnings.resetwarnings()
|
|
143
|
-
|
|
144
|
-
@pytest.fixture
|
|
145
|
-
def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
|
|
146
|
-
"""Create a unique temp directory for each test function."""
|
|
147
|
-
# Sanitize test name for filesystem compatibility
|
|
148
|
-
sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
|
|
149
|
-
uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
|
|
150
|
-
pathTemp = pathTempRoot / uniqueDirectory
|
|
151
|
-
pathTemp.mkdir(parents=True, exist_ok=True)
|
|
152
|
-
|
|
153
|
-
addTempFileToRegister(pathTemp)
|
|
154
|
-
return pathTemp
|
|
155
|
-
|
|
156
|
-
@pytest.fixture
|
|
157
|
-
def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
158
|
-
"""Temporarily replace the OEIS cache directory with a test directory."""
|
|
159
|
-
from mapFolding import oeis as there_must_be_a_better_way
|
|
160
|
-
pathCacheOriginal = there_must_be_a_better_way._pathCache
|
|
161
|
-
there_must_be_a_better_way._pathCache = pathTempTesting
|
|
162
|
-
yield pathTempTesting
|
|
163
|
-
there_must_be_a_better_way._pathCache = pathCacheOriginal
|
|
164
|
-
|
|
165
|
-
@pytest.fixture
|
|
166
|
-
def pathFilenameBenchmarksTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
|
|
167
|
-
"""Temporarily replace the benchmarks directory with a test directory."""
|
|
168
|
-
from mapFolding.benchmarks import benchmarking
|
|
169
|
-
pathFilenameOriginal = benchmarking.pathFilenameRecordedBenchmarks
|
|
170
|
-
pathFilenameTest = pathTempTesting / "benchmarks.npy"
|
|
171
|
-
benchmarking.pathFilenameRecordedBenchmarks = pathFilenameTest
|
|
172
|
-
yield pathFilenameTest
|
|
173
|
-
benchmarking.pathFilenameRecordedBenchmarks = pathFilenameOriginal
|
|
174
|
-
|
|
175
|
-
@pytest.fixture
|
|
176
|
-
def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
|
|
177
|
-
return pathTempTesting.joinpath("foldsTotalTest.txt")
|
|
178
|
-
|
|
179
|
-
"""
|
|
180
|
-
Section: Fixtures"""
|
|
181
|
-
|
|
182
|
-
@pytest.fixture
|
|
183
|
-
def foldsTotalKnown() -> Dict[Tuple[int,...], int]:
|
|
184
|
-
"""Returns a dictionary mapping dimension tuples to their known folding totals.
|
|
185
|
-
NOTE I am not convinced this is the best way to do this.
|
|
186
|
-
Advantage: I call `makeDictionaryFoldsTotalKnown()` from modules other than test modules.
|
|
187
|
-
Preference: I _think_ I would prefer a SSOT function available to any module
|
|
188
|
-
similar to `foldsTotalKnown = getFoldsTotalKnown(listDimensions)`."""
|
|
189
|
-
return makeDictionaryFoldsTotalKnown()
|
|
190
|
-
|
|
191
|
-
@pytest.fixture
|
|
192
|
-
def listDimensionsTestFunctionality(oeisID_1random: str) -> List[int]:
|
|
193
|
-
"""To test functionality, get one `listDimensions` from `valuesTestValidation` if
|
|
194
|
-
`validateListDimensions` approves. The algorithm can count the folds of the returned
|
|
195
|
-
`listDimensions` in a short enough time suitable for testing."""
|
|
196
|
-
while True:
|
|
197
|
-
n = random.choice(settingsOEIS[oeisID_1random]['valuesTestValidation'])
|
|
198
|
-
if n < 2:
|
|
199
|
-
continue
|
|
200
|
-
listDimensionsCandidate = settingsOEIS[oeisID_1random]['getDimensions'](n)
|
|
201
|
-
|
|
202
|
-
try:
|
|
203
|
-
return validateListDimensions(listDimensionsCandidate)
|
|
204
|
-
except (ValueError, NotImplementedError):
|
|
205
|
-
pass
|
|
206
|
-
|
|
207
|
-
@pytest.fixture
|
|
208
|
-
def listDimensionsTest_countFolds(oeisID: str) -> List[int]:
|
|
209
|
-
"""For each `oeisID` from the `pytest.fixture`, returns `listDimensions` from `valuesTestValidation`
|
|
210
|
-
if `validateListDimensions` approves. Each `listDimensions` is suitable for testing counts."""
|
|
211
|
-
while True:
|
|
212
|
-
n = random.choice(settingsOEIS[oeisID]['valuesTestValidation'])
|
|
213
|
-
if n < 2:
|
|
214
|
-
continue
|
|
215
|
-
listDimensionsCandidate = settingsOEIS[oeisID]['getDimensions'](n)
|
|
216
|
-
|
|
217
|
-
try:
|
|
218
|
-
return validateListDimensions(listDimensionsCandidate)
|
|
219
|
-
except (ValueError, NotImplementedError):
|
|
220
|
-
pass
|
|
221
|
-
|
|
222
|
-
@pytest.fixture
|
|
223
|
-
def mockBenchmarkTimer() -> Generator[unittest.mock.MagicMock | unittest.mock.AsyncMock, Any, None]:
|
|
224
|
-
"""Mock time.perf_counter_ns for consistent benchmark timing."""
|
|
225
|
-
with unittest.mock.patch('time.perf_counter_ns') as mockTimer:
|
|
226
|
-
mockTimer.side_effect = [0, 1e9] # Start and end times for 1 second
|
|
227
|
-
yield mockTimer
|
|
228
|
-
|
|
229
|
-
@pytest.fixture(params=oeisIDsImplemented)
|
|
230
|
-
def oeisID(request: pytest.FixtureRequest)-> str:
|
|
231
|
-
return request.param
|
|
232
|
-
|
|
233
|
-
@pytest.fixture
|
|
234
|
-
def oeisID_1random() -> str:
|
|
235
|
-
"""Return one random valid OEIS ID."""
|
|
236
|
-
return random.choice(oeisIDsImplemented)
|
|
237
|
-
|
|
238
|
-
@pytest.fixture
|
|
239
|
-
def mockFoldingFunction():
|
|
240
|
-
"""Creates a mock function that simulates _countFolds behavior."""
|
|
241
|
-
def make_mock(foldsValue: int, listDimensions: List[int]):
|
|
242
|
-
mock_array = makeDataContainer(2)
|
|
243
|
-
mock_array[0] = foldsValue
|
|
244
|
-
mock_array[-1] = getLeavesTotal(listDimensions)
|
|
245
|
-
|
|
246
|
-
def mock_countfolds(**keywordArguments):
|
|
247
|
-
keywordArguments['foldGroups'][:] = mock_array
|
|
248
|
-
return None
|
|
249
|
-
|
|
250
|
-
return mock_countfolds
|
|
251
|
-
return make_mock
|
|
252
|
-
|
|
253
|
-
"""
|
|
254
|
-
Section: Standardized test structures"""
|
|
255
|
-
|
|
256
|
-
def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
|
|
257
|
-
"""Format assertion message for any test comparison."""
|
|
258
|
-
return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
|
|
259
|
-
f"Expected: {expected}\n"
|
|
260
|
-
f"Got: {actual}")
|
|
261
|
-
|
|
262
|
-
def standardComparison(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
|
|
263
|
-
"""Template for tests expecting an error."""
|
|
264
|
-
if type(expected) == Type[Exception]:
|
|
265
|
-
messageExpected = expected.__name__
|
|
266
|
-
else:
|
|
267
|
-
messageExpected = expected
|
|
268
|
-
|
|
269
|
-
try:
|
|
270
|
-
messageActual = actual = functionTarget(*arguments)
|
|
271
|
-
except Exception as actualError:
|
|
272
|
-
messageActual = type(actualError).__name__
|
|
273
|
-
actual = type(actualError)
|
|
274
|
-
|
|
275
|
-
assert actual == expected, formatTestMessage(messageExpected, messageActual, functionTarget.__name__, *arguments)
|
|
276
|
-
|
|
277
|
-
def expectSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: Callable, *arguments: Any) -> None:
|
|
278
|
-
"""Template for tests expecting SystemExit.
|
|
279
|
-
|
|
280
|
-
Parameters
|
|
281
|
-
expected: Exit code expectation:
|
|
282
|
-
- "error": any non-zero exit code
|
|
283
|
-
- "nonError": specifically zero exit code
|
|
284
|
-
- int: exact exit code match
|
|
285
|
-
- Sequence[int]: exit code must be one of these values
|
|
286
|
-
functionTarget: The function to test
|
|
287
|
-
arguments: Arguments to pass to the function
|
|
288
|
-
"""
|
|
289
|
-
with pytest.raises(SystemExit) as exitInfo:
|
|
290
|
-
functionTarget(*arguments)
|
|
291
|
-
|
|
292
|
-
exitCode = exitInfo.value.code
|
|
293
|
-
|
|
294
|
-
if expected == "error":
|
|
295
|
-
assert exitCode != 0, \
|
|
296
|
-
f"Expected error exit (non-zero) but got code {exitCode}"
|
|
297
|
-
elif expected == "nonError":
|
|
298
|
-
assert exitCode == 0, \
|
|
299
|
-
f"Expected non-error exit (0) but got code {exitCode}"
|
|
300
|
-
elif isinstance(expected, (list, tuple)):
|
|
301
|
-
assert exitCode in expected, \
|
|
302
|
-
f"Expected exit code to be one of {expected} but got {exitCode}"
|
|
303
|
-
else:
|
|
304
|
-
assert exitCode == expected, \
|
|
305
|
-
f"Expected exit code {expected} but got {exitCode}"
|
|
306
|
-
|
|
307
|
-
def standardCacheTest(
|
|
308
|
-
expected: Any,
|
|
309
|
-
setupCacheFile: Optional[Callable[[pathlib.Path, str], None]],
|
|
310
|
-
oeisID: str,
|
|
311
|
-
pathCache: pathlib.Path
|
|
312
|
-
) -> None:
|
|
313
|
-
"""Template for tests involving OEIS cache operations.
|
|
314
|
-
|
|
315
|
-
Parameters
|
|
316
|
-
expected: Expected value or exception from _getOEISidValues
|
|
317
|
-
setupCacheFile: Function to prepare the cache file before test
|
|
318
|
-
oeisID: OEIS ID to test
|
|
319
|
-
pathCache: Temporary cache directory path
|
|
320
|
-
"""
|
|
321
|
-
pathFilenameCache = pathCache / _getFilenameOEISbFile(oeisID)
|
|
322
|
-
|
|
323
|
-
# Setup cache file if provided
|
|
324
|
-
if setupCacheFile:
|
|
325
|
-
setupCacheFile(pathFilenameCache, oeisID)
|
|
326
|
-
|
|
327
|
-
# Run test
|
|
328
|
-
try:
|
|
329
|
-
actual = _getOEISidValues(oeisID)
|
|
330
|
-
messageActual = actual
|
|
331
|
-
except Exception as actualError:
|
|
332
|
-
actual = type(actualError)
|
|
333
|
-
messageActual = type(actualError).__name__
|
|
334
|
-
|
|
335
|
-
# Compare results
|
|
336
|
-
if isinstance(expected, type) and issubclass(expected, Exception):
|
|
337
|
-
messageExpected = expected.__name__
|
|
338
|
-
assert isinstance(actual, expected), formatTestMessage(
|
|
339
|
-
messageExpected, messageActual, "_getOEISidValues", oeisID)
|
|
340
|
-
else:
|
|
341
|
-
messageExpected = expected
|
|
342
|
-
assert actual == expected, formatTestMessage(
|
|
343
|
-
messageExpected, messageActual, "_getOEISidValues", oeisID)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from mapFolding.theSSOT import *
|
tests/test_oeis.py
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
from tests.conftest import *
|
|
2
|
-
from contextlib import redirect_stdout
|
|
3
|
-
from datetime import datetime, timedelta
|
|
4
|
-
from typing import Optional, Tuple, Union
|
|
5
|
-
import io
|
|
6
|
-
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import pytest
|
|
9
|
-
import random
|
|
10
|
-
import re as regex
|
|
11
|
-
import unittest
|
|
12
|
-
import unittest.mock
|
|
13
|
-
import urllib.error
|
|
14
|
-
import urllib.request
|
|
15
|
-
|
|
16
|
-
def test_aOFn_calculate_value(oeisID: str):
|
|
17
|
-
for n in settingsOEIS[oeisID]['valuesTestValidation']:
|
|
18
|
-
standardComparison(settingsOEIS[oeisID]['valuesKnown'][n], oeisIDfor_n, oeisID, n)
|
|
19
|
-
|
|
20
|
-
@pytest.mark.parametrize("badID", ["A999999", " A999999 ", "A999999extra"])
|
|
21
|
-
def test__validateOEISid_invalid_id(badID: str):
|
|
22
|
-
standardComparison(KeyError, _validateOEISid, badID)
|
|
23
|
-
|
|
24
|
-
def test__validateOEISid_partially_valid(oeisID_1random: str):
|
|
25
|
-
standardComparison(KeyError, _validateOEISid, f"{oeisID_1random}extra")
|
|
26
|
-
|
|
27
|
-
def test__validateOEISid_valid_id(oeisID: str):
|
|
28
|
-
standardComparison(oeisID, _validateOEISid, oeisID)
|
|
29
|
-
|
|
30
|
-
def test__validateOEISid_valid_id_case_insensitive(oeisID: str):
|
|
31
|
-
standardComparison(oeisID.upper(), _validateOEISid, oeisID.lower())
|
|
32
|
-
standardComparison(oeisID.upper(), _validateOEISid, oeisID.upper())
|
|
33
|
-
standardComparison(oeisID.upper(), _validateOEISid, oeisID.swapcase())
|
|
34
|
-
|
|
35
|
-
parameters_test_aOFn_invalid_n = [
|
|
36
|
-
# (2, "ok"), # test the test template
|
|
37
|
-
(-random.randint(1, 100), "randomNegative"),
|
|
38
|
-
("foo", "string"),
|
|
39
|
-
(1.5, "float")
|
|
40
|
-
]
|
|
41
|
-
badValues, badValuesIDs = zip(*parameters_test_aOFn_invalid_n)
|
|
42
|
-
@pytest.mark.parametrize("badN", badValues, ids=badValuesIDs)
|
|
43
|
-
def test_aOFn_invalid_n(oeisID_1random: str, badN):
|
|
44
|
-
"""Check that negative or non-integer n raises ValueError."""
|
|
45
|
-
standardComparison(ValueError, oeisIDfor_n, oeisID_1random, badN)
|
|
46
|
-
|
|
47
|
-
def test_aOFn_zeroDim_A001418():
|
|
48
|
-
standardComparison(ArithmeticError, oeisIDfor_n, 'A001418', 0)
|
|
49
|
-
|
|
50
|
-
# ===== OEIS Cache Tests =====
|
|
51
|
-
@pytest.mark.parametrize("cacheExists", [True, False])
|
|
52
|
-
@unittest.mock.patch('pathlib.Path.exists')
|
|
53
|
-
@unittest.mock.patch('pathlib.Path.unlink')
|
|
54
|
-
def test_clearOEIScache(mock_unlink: unittest.mock.MagicMock, mock_exists: unittest.mock.MagicMock, cacheExists: bool):
|
|
55
|
-
"""Test OEIS cache clearing with both existing and non-existing cache."""
|
|
56
|
-
mock_exists.return_value = cacheExists
|
|
57
|
-
clearOEIScache()
|
|
58
|
-
|
|
59
|
-
if cacheExists:
|
|
60
|
-
assert mock_unlink.call_count == len(settingsOEIS)
|
|
61
|
-
mock_unlink.assert_has_calls([unittest.mock.call(missing_ok=True)] * len(settingsOEIS))
|
|
62
|
-
else:
|
|
63
|
-
mock_exists.assert_called_once()
|
|
64
|
-
mock_unlink.assert_not_called()
|
|
65
|
-
|
|
66
|
-
@pytest.mark.parametrize("scenarioCache", ["miss", "expired", "invalid"])
|
|
67
|
-
def testCacheScenarios(pathCacheTesting: pathlib.Path, oeisID_1random: str, scenarioCache: str) -> None:
|
|
68
|
-
"""Test cache scenarios: missing file, expired file, and invalid file."""
|
|
69
|
-
|
|
70
|
-
def setupCacheExpired(pathCache: pathlib.Path, oeisID: str) -> None:
|
|
71
|
-
pathCache.write_text("# Old cache content")
|
|
72
|
-
oldModificationTime = datetime.now() - timedelta(days=30)
|
|
73
|
-
os.utime(pathCache, times=(oldModificationTime.timestamp(), oldModificationTime.timestamp()))
|
|
74
|
-
|
|
75
|
-
def setupCacheInvalid(pathCache: pathlib.Path, oeisID: str) -> None:
|
|
76
|
-
pathCache.write_text("Invalid content")
|
|
77
|
-
|
|
78
|
-
if scenarioCache == "miss":
|
|
79
|
-
standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], None, oeisID_1random, pathCacheTesting)
|
|
80
|
-
elif scenarioCache == "expired":
|
|
81
|
-
standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupCacheExpired, oeisID_1random, pathCacheTesting)
|
|
82
|
-
else:
|
|
83
|
-
standardCacheTest(settingsOEIS[oeisID_1random]['valuesKnown'], setupCacheInvalid, oeisID_1random, pathCacheTesting)
|
|
84
|
-
|
|
85
|
-
def testInvalidFileContent(pathCacheTesting: pathlib.Path, oeisID_1random: str):
|
|
86
|
-
pathFilenameCache = pathCacheTesting / _getFilenameOEISbFile(oeisID=oeisID_1random)
|
|
87
|
-
|
|
88
|
-
# Write invalid content to cache
|
|
89
|
-
pathFilenameCache.write_text("# A999999\n1 1\n2 2\n")
|
|
90
|
-
modificationTimeOriginal = pathFilenameCache.stat().st_mtime
|
|
91
|
-
|
|
92
|
-
# Function should detect invalid content, fetch fresh data, and update cache
|
|
93
|
-
OEISsequence = _getOEISidValues(oeisID_1random)
|
|
94
|
-
|
|
95
|
-
# Verify the function succeeded
|
|
96
|
-
assert OEISsequence is not None
|
|
97
|
-
# Verify cache was updated (modification time changed)
|
|
98
|
-
assert pathFilenameCache.stat().st_mtime > modificationTimeOriginal
|
|
99
|
-
# Verify cache now contains correct sequence ID
|
|
100
|
-
assert f"# {oeisID_1random}" in pathFilenameCache.read_text()
|
|
101
|
-
|
|
102
|
-
def testParseContentErrors():
|
|
103
|
-
"""Test invalid content parsing."""
|
|
104
|
-
standardComparison(ValueError, _parseBFileOEIS, "Invalid content\n1 2\n", 'A001415')
|
|
105
|
-
|
|
106
|
-
def testExtraComments(pathCacheTesting: pathlib.Path, oeisID_1random: str):
|
|
107
|
-
pathFilenameCache = pathCacheTesting / _getFilenameOEISbFile(oeisID=oeisID_1random)
|
|
108
|
-
|
|
109
|
-
# Write content with extra comment lines
|
|
110
|
-
contentWithExtraComments = f"""# {oeisID_1random}
|
|
111
|
-
# Normal place for comment line 1
|
|
112
|
-
# Abnormal comment line
|
|
113
|
-
1 2
|
|
114
|
-
2 4
|
|
115
|
-
3 6
|
|
116
|
-
# Another comment in the middle
|
|
117
|
-
4 8
|
|
118
|
-
5 10"""
|
|
119
|
-
pathFilenameCache.write_text(contentWithExtraComments)
|
|
120
|
-
|
|
121
|
-
OEISsequence = _getOEISidValues(oeisID_1random)
|
|
122
|
-
# Verify sequence values are correct despite extra comments
|
|
123
|
-
standardComparison(2, lambda d: d[1], OEISsequence) # First value
|
|
124
|
-
standardComparison(8, lambda d: d[4], OEISsequence) # Value after mid-sequence comment
|
|
125
|
-
standardComparison(10, lambda d: d[5], OEISsequence) # Last value
|
|
126
|
-
|
|
127
|
-
def testNetworkError(monkeypatch: pytest.MonkeyPatch, pathCacheTesting: pathlib.Path):
|
|
128
|
-
"""Test network error handling."""
|
|
129
|
-
def mockUrlopen(*args, **kwargs):
|
|
130
|
-
raise urllib.error.URLError("Network error")
|
|
131
|
-
|
|
132
|
-
monkeypatch.setattr(urllib.request, 'urlopen', mockUrlopen)
|
|
133
|
-
standardComparison(urllib.error.URLError, _getOEISidValues, next(iter(settingsOEIS)))
|
|
134
|
-
|
|
135
|
-
# ===== Command Line Interface Tests =====
|
|
136
|
-
def testHelpText():
|
|
137
|
-
"""Test that help text is complete and examples are valid."""
|
|
138
|
-
outputStream = io.StringIO()
|
|
139
|
-
with redirect_stdout(outputStream):
|
|
140
|
-
getOEISids()
|
|
141
|
-
|
|
142
|
-
helpText = outputStream.getvalue()
|
|
143
|
-
|
|
144
|
-
# Verify content
|
|
145
|
-
for oeisID in oeisIDsImplemented:
|
|
146
|
-
assert oeisID in helpText
|
|
147
|
-
assert settingsOEIS[oeisID]['description'] in helpText
|
|
148
|
-
|
|
149
|
-
# Extract and verify examples
|
|
150
|
-
|
|
151
|
-
cliMatch = regex.search(r'OEIS_for_n (\w+) (\d+)', helpText)
|
|
152
|
-
pythonMatch = regex.search(r"oeisIDfor_n\('(\w+)', (\d+)\)", helpText)
|
|
153
|
-
|
|
154
|
-
assert cliMatch and pythonMatch, "Help text missing examples"
|
|
155
|
-
oeisID, n = pythonMatch.groups()
|
|
156
|
-
n = int(n)
|
|
157
|
-
|
|
158
|
-
# Verify CLI and Python examples use same values
|
|
159
|
-
assert cliMatch.groups() == (oeisID, str(n)), "CLI and Python examples inconsistent"
|
|
160
|
-
|
|
161
|
-
# Verify the example works
|
|
162
|
-
expectedValue = oeisIDfor_n(oeisID, n)
|
|
163
|
-
|
|
164
|
-
# Test CLI execution of the example
|
|
165
|
-
with unittest.mock.patch('sys.argv', ['OEIS_for_n', oeisID, str(n)]):
|
|
166
|
-
outputStream = io.StringIO()
|
|
167
|
-
with redirect_stdout(outputStream):
|
|
168
|
-
OEIS_for_n()
|
|
169
|
-
standardComparison(expectedValue, lambda: int(outputStream.getvalue().strip().split()[0]))
|
|
170
|
-
|
|
171
|
-
def testCLI_InvalidInputs():
|
|
172
|
-
"""Test CLI error handling."""
|
|
173
|
-
testCases = [
|
|
174
|
-
(['OEIS_for_n'], "missing arguments"),
|
|
175
|
-
(['OEIS_for_n', 'A999999', '1'], "invalid OEIS ID"),
|
|
176
|
-
(['OEIS_for_n', 'A001415', '-1'], "negative n"),
|
|
177
|
-
(['OEIS_for_n', 'A001415', 'abc'], "non-integer n"),
|
|
178
|
-
]
|
|
179
|
-
|
|
180
|
-
for arguments, testID in testCases:
|
|
181
|
-
with unittest.mock.patch('sys.argv', arguments):
|
|
182
|
-
expectSystemExit("error", OEIS_for_n)
|
|
183
|
-
|
|
184
|
-
def testCLI_HelpFlag():
|
|
185
|
-
"""Verify --help output contains required information."""
|
|
186
|
-
with unittest.mock.patch('sys.argv', ['OEIS_for_n', '--help']):
|
|
187
|
-
outputStream = io.StringIO()
|
|
188
|
-
with redirect_stdout(outputStream):
|
|
189
|
-
expectSystemExit("nonError", OEIS_for_n)
|
|
190
|
-
|
|
191
|
-
helpOutput = outputStream.getvalue()
|
|
192
|
-
assert "Available OEIS sequences:" in helpOutput
|
|
193
|
-
assert "Usage examples:" in helpOutput
|
|
194
|
-
assert all(oeisID in helpOutput for oeisID in oeisIDsImplemented)
|