mapFolding 0.13.1__py3-none-any.whl → 0.14.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/_oeisFormulas/A000136.py +4 -0
- mapFolding/_oeisFormulas/A000560.py +4 -0
- mapFolding/_oeisFormulas/A000682.py +17 -0
- mapFolding/_oeisFormulas/A005315.py +4 -0
- mapFolding/_oeisFormulas/A005316.py +10 -0
- mapFolding/_oeisFormulas/A223094.py +7 -0
- mapFolding/_oeisFormulas/A259702.py +4 -0
- mapFolding/_oeisFormulas/A301620.py +6 -0
- mapFolding/_oeisFormulas/Z0Z_aOFn.py +19 -0
- mapFolding/_oeisFormulas/Z0Z_oeisMeanders.py +53 -0
- mapFolding/_oeisFormulas/__init__.py +1 -0
- mapFolding/_oeisFormulas/matrixMeanders.py +65 -0
- mapFolding/_oeisFormulas/matrixMeandersAnnex.py +84 -0
- mapFolding/_theSSOT.py +68 -26
- mapFolding/basecamp.py +2 -2
- mapFolding/beDRY.py +1 -1
- mapFolding/oeis.py +56 -167
- mapFolding/reference/A005316JavaPort.py +134 -0
- mapFolding/reference/A005316imperative.py +101 -0
- mapFolding/reference/A005316intOptimized.py +122 -0
- mapFolding/reference/A005316optimized128bit.py +79 -0
- mapFolding/reference/A005316primitiveOptimized.py +97 -0
- mapFolding/reference/A005316redis.py +118 -0
- mapFolding/reference/A005316write2disk.py +169 -0
- mapFolding/reference/irvineJavaPort.py +4 -8
- mapFolding/reference/matrixMeandersBaseline.py +65 -0
- mapFolding/reference/matrixMeandersBaselineAnnex.py +84 -0
- mapFolding/someAssemblyRequired/makeJobTheorem2codon.py +45 -17
- mapFolding/tests/conftest.py +13 -13
- mapFolding/tests/test_computations.py +4 -4
- mapFolding/tests/test_oeis.py +7 -14
- mapfolding-0.14.0.dist-info/METADATA +78 -0
- {mapfolding-0.13.1.dist-info → mapfolding-0.14.0.dist-info}/RECORD +37 -15
- mapfolding-0.13.1.dist-info/METADATA +0 -154
- {mapfolding-0.13.1.dist-info → mapfolding-0.14.0.dist-info}/WHEEL +0 -0
- {mapfolding-0.13.1.dist-info → mapfolding-0.14.0.dist-info}/entry_points.txt +0 -0
- {mapfolding-0.13.1.dist-info → mapfolding-0.14.0.dist-info}/licenses/LICENSE +0 -0
- {mapfolding-0.13.1.dist-info → mapfolding-0.14.0.dist-info}/top_level.txt +0 -0
mapFolding/oeis.py
CHANGED
|
@@ -26,147 +26,58 @@ completes the journey from configuration foundation to mathematical discovery.
|
|
|
26
26
|
|
|
27
27
|
from collections.abc import Callable
|
|
28
28
|
from datetime import datetime, timedelta, UTC
|
|
29
|
-
from functools import cache
|
|
30
29
|
from hunterMakesPy import writeStringToHere
|
|
31
|
-
from
|
|
30
|
+
from itertools import chain
|
|
31
|
+
from mapFolding import countFolds
|
|
32
|
+
from mapFolding._theSSOT import cacheDays, pathCache, settingsOEISManuallySelected
|
|
32
33
|
from pathlib import Path
|
|
33
34
|
from typing import Any, Final, TypedDict
|
|
34
35
|
from urllib.request import urlopen
|
|
35
36
|
import argparse
|
|
36
|
-
import random
|
|
37
37
|
import sys
|
|
38
38
|
import time
|
|
39
39
|
import warnings
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
"""
|
|
43
|
-
|
|
44
|
-
pathCache: Path = packageSettings.pathPackage / ".cache"
|
|
45
|
-
"""Local directory path for storing cached OEIS sequence data and metadata."""
|
|
46
|
-
|
|
47
|
-
class SettingsOEIS(TypedDict):
|
|
48
|
-
"""Complete configuration settings for a single OEIS sequence implementation.
|
|
49
|
-
|
|
50
|
-
(AI generated docstring)
|
|
51
|
-
|
|
52
|
-
This `TypedDict` defines the structure for storing all metadata, known values, and operational parameters
|
|
53
|
-
needed to work with an OEIS sequence within the map folding context.
|
|
54
|
-
|
|
55
|
-
"""
|
|
41
|
+
class MetadataOEISid(TypedDict):
|
|
42
|
+
"""Settings for an implemented OEIS sequence."""
|
|
56
43
|
|
|
57
44
|
description: str
|
|
45
|
+
"""The OEIS.org description of the integer sequence."""
|
|
58
46
|
getMapShape: Callable[[int], tuple[int, ...]]
|
|
47
|
+
"""Function to convert the OEIS sequence index, 'n', to its `mapShape` tuple."""
|
|
59
48
|
offset: int
|
|
49
|
+
"""The starting index, 'n', of the sequence, typically 0 or 1."""
|
|
60
50
|
valuesBenchmark: list[int]
|
|
51
|
+
"""List of index values, 'n', to use when benchmarking the algorithm performance."""
|
|
61
52
|
valuesKnown: dict[int, int]
|
|
53
|
+
"""Dictionary of sequence indices, 'n', to their known values, `foldsTotal`."""
|
|
62
54
|
valuesTestParallelization: list[int]
|
|
55
|
+
"""List of index values, 'n', to use when testing parallelization performance."""
|
|
63
56
|
valuesTestValidation: list[int]
|
|
57
|
+
"""List of index values, 'n', to use when testing validation performance."""
|
|
64
58
|
valueUnknown: int
|
|
59
|
+
"""The smallest value of 'n' for for which `foldsTotal` is unknown."""
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
"""Hardcoded configuration values for OEIS sequences defined within the module.
|
|
68
|
-
|
|
69
|
-
(AI generated docstring)
|
|
70
|
-
|
|
71
|
-
This `TypedDict` contains the static configuration data that is embedded in the source code
|
|
72
|
-
rather than retrieved from external sources.
|
|
73
|
-
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
getMapShape: Callable[[int], tuple[int, ...]]
|
|
77
|
-
valuesBenchmark: list[int]
|
|
78
|
-
valuesTestParallelization: list[int]
|
|
79
|
-
valuesTestValidation: list[int]
|
|
80
|
-
|
|
81
|
-
settingsOEIShardcodedValues: dict[str, SettingsOEIShardcodedValues] = {
|
|
82
|
-
'A000136': {
|
|
83
|
-
'getMapShape': lambda n: tuple(sorted([1, n])),
|
|
84
|
-
'valuesBenchmark': [14],
|
|
85
|
-
'valuesTestParallelization': [*range(3, 7)],
|
|
86
|
-
'valuesTestValidation': [random.randint(2, 9)], # noqa: S311
|
|
87
|
-
},
|
|
88
|
-
'A001415': {
|
|
89
|
-
'getMapShape': lambda n: tuple(sorted([2, n])),
|
|
90
|
-
'valuesBenchmark': [14],
|
|
91
|
-
'valuesTestParallelization': [*range(3, 7)],
|
|
92
|
-
'valuesTestValidation': [random.randint(2, 9)], # noqa: S311
|
|
93
|
-
},
|
|
94
|
-
'A001416': {
|
|
95
|
-
'getMapShape': lambda n: tuple(sorted([3, n])),
|
|
96
|
-
'valuesBenchmark': [9],
|
|
97
|
-
'valuesTestParallelization': [*range(3, 5)],
|
|
98
|
-
'valuesTestValidation': [random.randint(2, 6)], # noqa: S311
|
|
99
|
-
},
|
|
100
|
-
'A001417': {
|
|
101
|
-
'getMapShape': lambda n: tuple(2 for _dimension in range(n)),
|
|
102
|
-
'valuesBenchmark': [6],
|
|
103
|
-
'valuesTestParallelization': [*range(2, 4)],
|
|
104
|
-
'valuesTestValidation': [random.randint(2, 4)], # noqa: S311
|
|
105
|
-
},
|
|
106
|
-
'A195646': {
|
|
107
|
-
'getMapShape': lambda n: tuple(3 for _dimension in range(n)),
|
|
108
|
-
'valuesBenchmark': [3],
|
|
109
|
-
'valuesTestParallelization': [*range(2, 3)],
|
|
110
|
-
'valuesTestValidation': [2],
|
|
111
|
-
},
|
|
112
|
-
'A001418': {
|
|
113
|
-
'getMapShape': lambda n: (n, n),
|
|
114
|
-
'valuesBenchmark': [5],
|
|
115
|
-
'valuesTestParallelization': [*range(2, 4)],
|
|
116
|
-
'valuesTestValidation': [random.randint(2, 4)], # noqa: S311
|
|
117
|
-
},
|
|
118
|
-
}
|
|
119
|
-
"""
|
|
120
|
-
Registry of hardcoded OEIS sequence configurations implemented in this module.
|
|
121
|
-
|
|
122
|
-
Each key is a standardized OEIS sequence identifier (e.g., 'A001415'), and each value contains
|
|
123
|
-
the static configuration needed to work with that sequence, including the mapping function from
|
|
124
|
-
sequence index to map shape and various test parameter sets.
|
|
125
|
-
"""
|
|
126
|
-
|
|
127
|
-
oeisIDsImplemented: Final[list[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues])
|
|
61
|
+
oeisIDsImplemented: Final[list[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEISManuallySelected])
|
|
128
62
|
"""Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
|
|
129
63
|
|
|
130
|
-
def
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
(AI generated docstring)
|
|
134
|
-
|
|
135
|
-
If the provided ID is recognized within the application's implemented OEIS sequences, the function returns the
|
|
136
|
-
verified ID in uppercase. Otherwise, a KeyError is raised indicating that the sequence is not directly supported.
|
|
64
|
+
def _standardizeOEISid(oeisID: str) -> str:
|
|
65
|
+
"""Standardize an OEIS sequence ID to uppercase and without whitespace.
|
|
137
66
|
|
|
138
67
|
Parameters
|
|
139
68
|
----------
|
|
140
|
-
|
|
141
|
-
The OEIS sequence identifier to
|
|
69
|
+
oeisID : str
|
|
70
|
+
The OEIS sequence identifier to standardize.
|
|
142
71
|
|
|
143
72
|
Returns
|
|
144
73
|
-------
|
|
145
|
-
str
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Raises
|
|
149
|
-
------
|
|
150
|
-
KeyError
|
|
151
|
-
If the provided sequence ID is not directly implemented.
|
|
74
|
+
oeisIDstandardized : str
|
|
75
|
+
Uppercase, alphanumeric OEIS ID.
|
|
152
76
|
|
|
153
77
|
"""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
oeisIDcleaned: str = str(oeisIDcandidate).upper().strip()
|
|
158
|
-
if oeisIDcleaned in oeisIDsImplemented:
|
|
159
|
-
return oeisIDcleaned
|
|
160
|
-
else:
|
|
161
|
-
message = (
|
|
162
|
-
f"OEIS ID {oeisIDcandidate} is not directly implemented.\n"
|
|
163
|
-
f"Available sequences:\n{_formatOEISsequenceInfo()}"
|
|
164
|
-
)
|
|
165
|
-
raise KeyError(
|
|
166
|
-
message
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
def getFilenameOEISbFile(oeisID: str) -> str:
|
|
78
|
+
return str(oeisID).upper().strip()
|
|
79
|
+
|
|
80
|
+
def _getFilenameOEISbFile(oeisID: str) -> str:
|
|
170
81
|
"""Generate the filename for an OEIS b-file given a sequence ID.
|
|
171
82
|
|
|
172
83
|
(AI generated docstring)
|
|
@@ -185,7 +96,7 @@ def getFilenameOEISbFile(oeisID: str) -> str:
|
|
|
185
96
|
The corresponding b-file filename for the given sequence ID.
|
|
186
97
|
|
|
187
98
|
"""
|
|
188
|
-
oeisID =
|
|
99
|
+
oeisID = _standardizeOEISid(oeisID)
|
|
189
100
|
return f"b{oeisID[1:]}.txt"
|
|
190
101
|
|
|
191
102
|
def _parseBFileOEIS(OEISbFile: str) -> dict[int, int]:
|
|
@@ -222,7 +133,7 @@ def _parseBFileOEIS(OEISbFile: str) -> dict[int, int]:
|
|
|
222
133
|
OEISsequence[n] = aOFn
|
|
223
134
|
return OEISsequence
|
|
224
135
|
|
|
225
|
-
def
|
|
136
|
+
def _getOEISofficial(pathFilenameCache: Path, url: str) -> None | str:
|
|
226
137
|
"""Retrieve OEIS sequence data from cache or online source with intelligent caching.
|
|
227
138
|
|
|
228
139
|
(AI generated docstring)
|
|
@@ -263,7 +174,8 @@ def getOEISofficial(pathFilenameCache: Path, url: str) -> None | str:
|
|
|
263
174
|
|
|
264
175
|
if not tryCache:
|
|
265
176
|
if not url.startswith(("http:", "https:")):
|
|
266
|
-
|
|
177
|
+
message = "URL must start with 'http:' or 'https:'"
|
|
178
|
+
raise ValueError(message)
|
|
267
179
|
with urlopen(url) as response:
|
|
268
180
|
oeisInformationRaw = response.read().decode('utf-8')
|
|
269
181
|
oeisInformation = str(oeisInformationRaw)
|
|
@@ -302,10 +214,10 @@ def getOEISidValues(oeisID: str) -> dict[int, int]:
|
|
|
302
214
|
If there is an error reading from or writing to the local cache.
|
|
303
215
|
|
|
304
216
|
"""
|
|
305
|
-
pathFilenameCache: Path = pathCache /
|
|
306
|
-
url: str = f"https://oeis.org/{oeisID}/{
|
|
217
|
+
pathFilenameCache: Path = pathCache / _getFilenameOEISbFile(oeisID)
|
|
218
|
+
url: str = f"https://oeis.org/{oeisID}/{_getFilenameOEISbFile(oeisID)}"
|
|
307
219
|
|
|
308
|
-
oeisInformation: None | str =
|
|
220
|
+
oeisInformation: None | str = _getOEISofficial(pathFilenameCache, url)
|
|
309
221
|
|
|
310
222
|
if oeisInformation:
|
|
311
223
|
return _parseBFileOEIS(oeisInformation)
|
|
@@ -338,11 +250,11 @@ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
|
338
250
|
be retrieved, warning messages are issued and fallback values are returned.
|
|
339
251
|
|
|
340
252
|
"""
|
|
341
|
-
oeisID =
|
|
253
|
+
oeisID = _standardizeOEISid(oeisID)
|
|
342
254
|
pathFilenameCache: Path = pathCache / f"{oeisID}.txt"
|
|
343
255
|
url: str = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
|
|
344
256
|
|
|
345
|
-
oeisInformation: None | str =
|
|
257
|
+
oeisInformation: None | str = _getOEISofficial(pathFilenameCache, url)
|
|
346
258
|
|
|
347
259
|
if not oeisInformation:
|
|
348
260
|
return "Not found", -1
|
|
@@ -350,7 +262,7 @@ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
|
350
262
|
offset = None
|
|
351
263
|
for lineOEIS in oeisInformation.splitlines():
|
|
352
264
|
lineOEIS = lineOEIS.strip() # noqa: PLW2901
|
|
353
|
-
if not lineOEIS or len(lineOEIS.split()) < 3:
|
|
265
|
+
if not lineOEIS or len(lineOEIS.split()) < 3:
|
|
354
266
|
continue
|
|
355
267
|
fieldCode, sequenceID, fieldData = lineOEIS.split(maxsplit=2)
|
|
356
268
|
if fieldCode == '%N' and sequenceID == oeisID:
|
|
@@ -367,7 +279,7 @@ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
|
|
|
367
279
|
description: str = ' '.join(listDescriptionDeconstructed)
|
|
368
280
|
return description, offset
|
|
369
281
|
|
|
370
|
-
def
|
|
282
|
+
def _makeDictionaryOEIS() -> dict[str, MetadataOEISid]:
|
|
371
283
|
"""Construct the comprehensive settings dictionary for all implemented OEIS sequences.
|
|
372
284
|
|
|
373
285
|
(AI generated docstring)
|
|
@@ -390,60 +302,37 @@ def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
|
|
|
390
302
|
objects, containing all metadata and known values needed for computation and validation.
|
|
391
303
|
|
|
392
304
|
"""
|
|
393
|
-
|
|
305
|
+
dictionaryOEIS: dict[str, MetadataOEISid] = {}
|
|
394
306
|
for oeisID in oeisIDsImplemented:
|
|
395
307
|
valuesKnownSherpa: dict[int, int] = getOEISidValues(oeisID)
|
|
396
308
|
descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
|
|
397
|
-
|
|
309
|
+
dictionaryOEIS[oeisID] = MetadataOEISid(
|
|
398
310
|
description=descriptionSherpa,
|
|
399
311
|
offset=offsetSherpa,
|
|
400
|
-
getMapShape=
|
|
401
|
-
valuesBenchmark=
|
|
402
|
-
valuesTestParallelization=
|
|
403
|
-
valuesTestValidation=
|
|
312
|
+
getMapShape=settingsOEISManuallySelected[oeisID]['getMapShape'],
|
|
313
|
+
valuesBenchmark=settingsOEISManuallySelected[oeisID]['valuesBenchmark'],
|
|
314
|
+
valuesTestParallelization=settingsOEISManuallySelected[oeisID]['valuesTestParallelization'],
|
|
315
|
+
valuesTestValidation=settingsOEISManuallySelected[oeisID]['valuesTestValidation'] + list(range(offsetSherpa, 2)),
|
|
404
316
|
valuesKnown=valuesKnownSherpa,
|
|
405
317
|
valueUnknown=max(valuesKnownSherpa.keys(), default=0) + 1
|
|
406
318
|
)
|
|
407
|
-
return
|
|
319
|
+
return dictionaryOEIS
|
|
408
320
|
|
|
409
|
-
|
|
410
|
-
"""
|
|
411
|
-
Complete settings and metadata for all implemented OEIS sequences.
|
|
412
|
-
|
|
413
|
-
This dictionary contains the comprehensive configuration for each OEIS sequence supported by the module,
|
|
414
|
-
including known values retrieved from OEIS, mathematical descriptions, offset information, and all
|
|
415
|
-
operational parameters needed for computation and testing. The dictionary is populated by combining
|
|
416
|
-
hardcoded configurations with dynamically retrieved OEIS data during module initialization.
|
|
417
|
-
"""
|
|
321
|
+
dictionaryOEIS: dict[str, MetadataOEISid] = _makeDictionaryOEIS()
|
|
322
|
+
"""Metadata for each OEIS sequence ID."""
|
|
418
323
|
|
|
419
|
-
@cache
|
|
420
324
|
def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
|
|
421
|
-
"""
|
|
422
|
-
|
|
423
|
-
(AI generated docstring)
|
|
424
|
-
|
|
425
|
-
This function processes all known sequence values from implemented OEIS sequences and creates
|
|
426
|
-
a unified dictionary that maps map dimension tuples to their corresponding folding totals. The
|
|
427
|
-
resulting dictionary enables rapid lookup of known values without requiring knowledge of which
|
|
428
|
-
specific OEIS sequence contains the data.
|
|
325
|
+
"""Make a `mapShape` to known `foldsTotal` dictionary.
|
|
429
326
|
|
|
430
327
|
Returns
|
|
431
328
|
-------
|
|
432
|
-
|
|
329
|
+
dictionaryFoldsTotalKnown : dict[tuple[int, ...], int]
|
|
433
330
|
A dictionary where keys are tuples representing map shapes and values are the total number
|
|
434
331
|
of distinct folding patterns for those shapes.
|
|
435
332
|
|
|
436
333
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
for settings in settingsOEIS.values():
|
|
440
|
-
sequence = settings['valuesKnown']
|
|
441
|
-
|
|
442
|
-
for n, foldingsTotal in sequence.items():
|
|
443
|
-
mapShape = settings['getMapShape'](n)
|
|
444
|
-
mapShape = tuple(mapShape)
|
|
445
|
-
dictionaryMapDimensionsToFoldsTotalKnown[mapShape] = foldingsTotal
|
|
446
|
-
return dictionaryMapDimensionsToFoldsTotalKnown
|
|
334
|
+
return dict(chain.from_iterable(zip(map(oeisID['getMapShape'], oeisID['valuesKnown'].keys())
|
|
335
|
+
, oeisID['valuesKnown'].values(), strict=True) for oeisID in dictionaryOEIS.values()))
|
|
447
336
|
|
|
448
337
|
def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
|
|
449
338
|
"""Retrieve the known total number of distinct folding patterns for a given map shape.
|
|
@@ -489,7 +378,7 @@ def _formatHelpText() -> str:
|
|
|
489
378
|
|
|
490
379
|
"""
|
|
491
380
|
exampleOEISid: str = oeisIDsImplemented[0]
|
|
492
|
-
exampleN: int =
|
|
381
|
+
exampleN: int = dictionaryOEIS[exampleOEISid]['valuesTestValidation'][-1]
|
|
493
382
|
|
|
494
383
|
return (
|
|
495
384
|
"\nAvailable OEIS sequences:\n"
|
|
@@ -517,7 +406,7 @@ def _formatOEISsequenceInfo() -> str:
|
|
|
517
406
|
|
|
518
407
|
"""
|
|
519
408
|
return "\n".join(
|
|
520
|
-
f" {oeisID}: {
|
|
409
|
+
f" {oeisID}: {dictionaryOEIS[oeisID]['description']}"
|
|
521
410
|
for oeisID in oeisIDsImplemented
|
|
522
411
|
)
|
|
523
412
|
|
|
@@ -553,20 +442,20 @@ def oeisIDfor_n(oeisID: str, n: int | Any) -> int:
|
|
|
553
442
|
If n is below the sequence's defined offset.
|
|
554
443
|
|
|
555
444
|
"""
|
|
556
|
-
oeisID =
|
|
445
|
+
oeisID = _standardizeOEISid(oeisID)
|
|
557
446
|
|
|
558
447
|
if not isinstance(n, int) or n < 0:
|
|
559
448
|
message = f"I received `{n = }` in the form of `{type(n) = }`, but it must be non-negative integer in the form of `{int}`."
|
|
560
449
|
raise ValueError(message)
|
|
561
450
|
|
|
562
|
-
mapShape: tuple[int, ...] =
|
|
451
|
+
mapShape: tuple[int, ...] = dictionaryOEIS[oeisID]['getMapShape'](n)
|
|
563
452
|
|
|
564
|
-
if n <= 1 or len(mapShape) < 2:
|
|
565
|
-
offset: int =
|
|
453
|
+
if n <= 1 or len(mapShape) < 2:
|
|
454
|
+
offset: int = dictionaryOEIS[oeisID]['offset']
|
|
566
455
|
if n < offset:
|
|
567
456
|
message = f"OEIS sequence {oeisID} is not defined at {n = }."
|
|
568
457
|
raise ArithmeticError(message)
|
|
569
|
-
foldsTotal: int =
|
|
458
|
+
foldsTotal: int = dictionaryOEIS[oeisID]['valuesKnown'][n]
|
|
570
459
|
return foldsTotal
|
|
571
460
|
return countFolds(mapShape)
|
|
572
461
|
|
|
@@ -630,9 +519,9 @@ def clearOEIScache() -> None:
|
|
|
630
519
|
if not pathCache.exists():
|
|
631
520
|
print(f"Cache directory, {pathCache}, not found - nothing to clear.") # noqa: T201
|
|
632
521
|
return
|
|
633
|
-
for oeisID in
|
|
522
|
+
for oeisID in dictionaryOEIS:
|
|
634
523
|
( pathCache / f"{oeisID}.txt" ).unlink(missing_ok=True)
|
|
635
|
-
( pathCache /
|
|
524
|
+
( pathCache / _getFilenameOEISbFile(oeisID) ).unlink(missing_ok=True)
|
|
636
525
|
print(f"Cache cleared from {pathCache}") # noqa: T201
|
|
637
526
|
|
|
638
527
|
def getOEISids() -> None:
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from hunterMakesPy import raiseIfNone
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
# Constants for bit manipulation
|
|
6
|
+
WORD_SHIFT: Final[int] = 2
|
|
7
|
+
ODD_BITS: Final[int] = 0x5555555555555555
|
|
8
|
+
|
|
9
|
+
class BasicMeanderProblem:
|
|
10
|
+
"""Processing component to determine number of meanders."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, remainingBridges: int) -> None:
|
|
13
|
+
self.remainingBridges = remainingBridges
|
|
14
|
+
self.archStateLimit = 1 << (2 + (WORD_SHIFT * (remainingBridges + 1)))
|
|
15
|
+
self.bridgesTotalIsOdd = (remainingBridges & 1) == 1
|
|
16
|
+
|
|
17
|
+
def initialStates(self) -> list[int]:
|
|
18
|
+
"""Initialize states to enumerate open meanders (A005316)."""
|
|
19
|
+
if self.bridgesTotalIsOdd:
|
|
20
|
+
bitPattern = (1 << WORD_SHIFT) | 1
|
|
21
|
+
bitPattern <<= WORD_SHIFT
|
|
22
|
+
return [bitPattern | 1 << 1]
|
|
23
|
+
else:
|
|
24
|
+
bitPattern = (1 << WORD_SHIFT) | 1
|
|
25
|
+
return [bitPattern | bitPattern << 1]
|
|
26
|
+
|
|
27
|
+
def semiMeanderInitialStates(self) -> list[int]:
|
|
28
|
+
"""Initialize states used to enumerate semi-meanders (A000682)."""
|
|
29
|
+
initialStatesList: list[int] = []
|
|
30
|
+
bitPattern = 1 if self.bridgesTotalIsOdd else ((1 << WORD_SHIFT) | 1)
|
|
31
|
+
|
|
32
|
+
packedState = bitPattern | bitPattern << 1
|
|
33
|
+
while packedState < self.archStateLimit:
|
|
34
|
+
initialStatesList.append(packedState)
|
|
35
|
+
bitPattern = ((bitPattern << WORD_SHIFT) | 1) << WORD_SHIFT | 1
|
|
36
|
+
packedState = bitPattern | bitPattern << 1
|
|
37
|
+
|
|
38
|
+
return initialStatesList
|
|
39
|
+
|
|
40
|
+
def enumerate(self, packedState: int) -> list[int]: # noqa: C901
|
|
41
|
+
"""Enumerate next states from previous state."""
|
|
42
|
+
bitMask = ODD_BITS
|
|
43
|
+
bitWidth = 64
|
|
44
|
+
while bitMask < packedState:
|
|
45
|
+
bitMask |= bitMask << bitWidth
|
|
46
|
+
bitWidth += bitWidth
|
|
47
|
+
lower: int = packedState & bitMask
|
|
48
|
+
upper: int = (packedState - lower) >> 1
|
|
49
|
+
nextStatesList: list[int] = []
|
|
50
|
+
|
|
51
|
+
# Leg crosses from below road to above road
|
|
52
|
+
if lower != 1:
|
|
53
|
+
nextState: int = (lower >> WORD_SHIFT | (((upper << WORD_SHIFT) ^ (1 if (lower & 1) == 0 else 0)) << 1))
|
|
54
|
+
if nextState < self.archStateLimit:
|
|
55
|
+
nextStatesList.append(nextState)
|
|
56
|
+
|
|
57
|
+
# Leg crosses from above road to below road
|
|
58
|
+
if upper != 1:
|
|
59
|
+
nextState = (((lower << WORD_SHIFT) ^ (1 if (upper & 1) == 0 else 0)) | (upper >> WORD_SHIFT) << 1)
|
|
60
|
+
if nextState < self.archStateLimit:
|
|
61
|
+
nextStatesList.append(nextState)
|
|
62
|
+
|
|
63
|
+
# Introduction of new arch
|
|
64
|
+
nextState = ((lower << WORD_SHIFT) | 1 | ((upper << WORD_SHIFT) | 1) << 1)
|
|
65
|
+
if nextState < self.archStateLimit:
|
|
66
|
+
nextStatesList.append(nextState)
|
|
67
|
+
|
|
68
|
+
# Arch connection, only for JOIN_ARCH, not CLOSE_LOOP for semi-meanders
|
|
69
|
+
if lower != 1 and upper != 1 and ((lower & 1) == 0 or (upper & 1) == 0): # JOIN_ARCH condition
|
|
70
|
+
if (lower & 1) == 0 and (upper & 1) == 1:
|
|
71
|
+
archBalance = 0
|
|
72
|
+
bitPosition = 1
|
|
73
|
+
while archBalance >= 0:
|
|
74
|
+
bitPosition <<= WORD_SHIFT
|
|
75
|
+
archBalance += 1 if (lower & bitPosition) == 0 else -1
|
|
76
|
+
lower ^= bitPosition
|
|
77
|
+
if (upper & 1) == 0 and (lower & 1) == 1:
|
|
78
|
+
archBalance = 0
|
|
79
|
+
bitPosition = 1
|
|
80
|
+
while archBalance >= 0:
|
|
81
|
+
bitPosition <<= WORD_SHIFT
|
|
82
|
+
archBalance += 1 if (upper & bitPosition) == 0 else -1
|
|
83
|
+
upper ^= bitPosition
|
|
84
|
+
nextState = (lower >> WORD_SHIFT | (upper >> WORD_SHIFT) << 1)
|
|
85
|
+
if nextState < self.archStateLimit:
|
|
86
|
+
nextStatesList.append(nextState)
|
|
87
|
+
|
|
88
|
+
return nextStatesList
|
|
89
|
+
|
|
90
|
+
class SimpleProcessor:
|
|
91
|
+
"""Simple processing engine for state enumeration."""
|
|
92
|
+
|
|
93
|
+
def __init__(self) -> None:
|
|
94
|
+
self.createStateMachine: type | None = None
|
|
95
|
+
self.totalTransitions = 0
|
|
96
|
+
|
|
97
|
+
def setCreateStateMachine(self, stateMachineCreator: type) -> None:
|
|
98
|
+
"""Set state creation machine."""
|
|
99
|
+
self.createStateMachine = stateMachineCreator
|
|
100
|
+
|
|
101
|
+
def process(self, bridgesCount: int, initialStates: Iterable[int]) -> int:
|
|
102
|
+
"""Process initial states down to final count."""
|
|
103
|
+
stateCounts: list[tuple[int, int]] = [(state, 1) for state in initialStates]
|
|
104
|
+
|
|
105
|
+
self.createStateMachine = raiseIfNone(self.createStateMachine, "State machine creator must be set before processing.")
|
|
106
|
+
bridgesRemaining: int = bridgesCount
|
|
107
|
+
while bridgesRemaining > 0:
|
|
108
|
+
bridgesRemaining -= 1
|
|
109
|
+
stateCounts = self._accumulate(self.createStateMachine(bridgesRemaining), stateCounts)
|
|
110
|
+
|
|
111
|
+
return sum(count for state, count in stateCounts)
|
|
112
|
+
|
|
113
|
+
def _accumulate(self, layer: BasicMeanderProblem, previousCounts: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
|
114
|
+
"""Accumulate state transitions for one layer."""
|
|
115
|
+
stateCountsDict: dict[int, int] = {}
|
|
116
|
+
transitions: int = 0
|
|
117
|
+
|
|
118
|
+
for state, count in previousCounts:
|
|
119
|
+
for nextState in layer.enumerate(state):
|
|
120
|
+
if nextState in stateCountsDict:
|
|
121
|
+
stateCountsDict[nextState] += count
|
|
122
|
+
else:
|
|
123
|
+
stateCountsDict[nextState] = count
|
|
124
|
+
transitions += 1
|
|
125
|
+
|
|
126
|
+
self.totalTransitions += transitions
|
|
127
|
+
return list(stateCountsDict.items())
|
|
128
|
+
|
|
129
|
+
def A005316(n: int) -> int:
|
|
130
|
+
"""Meandric numbers: number of ways a river can cross a road n times."""
|
|
131
|
+
processor = SimpleProcessor()
|
|
132
|
+
processor.setCreateStateMachine(BasicMeanderProblem)
|
|
133
|
+
meanderProblem = BasicMeanderProblem(n)
|
|
134
|
+
return processor.process(n, meanderProblem.initialStates())
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
def count(bridges: int, dictionaryStateToTotal: dict[int, int]) -> int: # noqa: C901, PLR0912
|
|
2
|
+
while bridges > 0:
|
|
3
|
+
bridges -= 1
|
|
4
|
+
archStateLimit = 1 << (2 + (2 * (bridges + 1)))
|
|
5
|
+
|
|
6
|
+
dictionaryStateToTotalNext: dict[int, int] = {}
|
|
7
|
+
|
|
8
|
+
for state, totalCurrent in dictionaryStateToTotal.items():
|
|
9
|
+
maskBits = 0x5555555555555555
|
|
10
|
+
if state > maskBits:
|
|
11
|
+
bitWidth = 64
|
|
12
|
+
while maskBits < state:
|
|
13
|
+
maskBits |= maskBits << bitWidth
|
|
14
|
+
bitWidth <<= 1
|
|
15
|
+
|
|
16
|
+
lower = state & maskBits
|
|
17
|
+
upper = (state ^ lower) >> 1
|
|
18
|
+
|
|
19
|
+
listNextStates: list[int] = []
|
|
20
|
+
|
|
21
|
+
lower_not_one = lower != 1
|
|
22
|
+
upper_not_one = upper != 1
|
|
23
|
+
lower_even = (lower & 1) == 0
|
|
24
|
+
upper_even = (upper & 1) == 0
|
|
25
|
+
|
|
26
|
+
if lower_not_one:
|
|
27
|
+
nextState = (lower >> 2) | (((upper << 2) ^ (1 if lower_even else 0)) << 1)
|
|
28
|
+
if nextState < archStateLimit:
|
|
29
|
+
listNextStates.append(nextState)
|
|
30
|
+
|
|
31
|
+
if upper_not_one:
|
|
32
|
+
nextState = ((lower << 2) ^ (1 if upper_even else 0)) | ((upper >> 2) << 1)
|
|
33
|
+
if nextState < archStateLimit:
|
|
34
|
+
listNextStates.append(nextState)
|
|
35
|
+
|
|
36
|
+
nextState = ((lower << 2) | 1) | (((upper << 2) | 1) << 1)
|
|
37
|
+
if nextState < archStateLimit:
|
|
38
|
+
listNextStates.append(nextState)
|
|
39
|
+
|
|
40
|
+
if lower_not_one and upper_not_one and (lower_even or upper_even):
|
|
41
|
+
temp_lower, temp_upper = lower, upper
|
|
42
|
+
|
|
43
|
+
if lower_even and not upper_even:
|
|
44
|
+
archBalance = 0
|
|
45
|
+
bitPosition = 1
|
|
46
|
+
while archBalance >= 0:
|
|
47
|
+
bitPosition <<= 2
|
|
48
|
+
archBalance += 1 if (temp_lower & bitPosition) == 0 else -1
|
|
49
|
+
temp_lower ^= bitPosition
|
|
50
|
+
|
|
51
|
+
if upper_even and not lower_even:
|
|
52
|
+
archBalance = 0
|
|
53
|
+
bitPosition = 1
|
|
54
|
+
while archBalance >= 0:
|
|
55
|
+
bitPosition <<= 2
|
|
56
|
+
archBalance += 1 if (temp_upper & bitPosition) == 0 else -1
|
|
57
|
+
temp_upper ^= bitPosition
|
|
58
|
+
|
|
59
|
+
nextState = (temp_lower >> 2) | ((temp_upper >> 2) << 1)
|
|
60
|
+
if nextState < archStateLimit:
|
|
61
|
+
listNextStates.append(nextState)
|
|
62
|
+
|
|
63
|
+
for nextState in listNextStates:
|
|
64
|
+
dictionaryStateToTotalNext[nextState] = dictionaryStateToTotalNext.get(nextState, 0) + totalCurrent
|
|
65
|
+
|
|
66
|
+
dictionaryStateToTotal = dictionaryStateToTotalNext
|
|
67
|
+
|
|
68
|
+
return sum(dictionaryStateToTotal.values())
|
|
69
|
+
|
|
70
|
+
def initializeA005316(remainingBridges: int) -> dict[int, int]:
|
|
71
|
+
bridgesTotalIsOdd = (remainingBridges & 1) == 1
|
|
72
|
+
if bridgesTotalIsOdd:
|
|
73
|
+
arrayBitPattern = (1 << 2) | 1
|
|
74
|
+
arrayBitPattern <<= 2
|
|
75
|
+
initialState = arrayBitPattern | 1 << 1
|
|
76
|
+
return {initialState: 1}
|
|
77
|
+
else:
|
|
78
|
+
arrayBitPattern = (1 << 2) | 1
|
|
79
|
+
initialState = arrayBitPattern | arrayBitPattern << 1
|
|
80
|
+
return {initialState: 1}
|
|
81
|
+
|
|
82
|
+
def initializeA000682(remainingBridges: int) -> dict[int, int]:
|
|
83
|
+
bridgesTotalIsOdd = (remainingBridges & 1) == 1
|
|
84
|
+
archStateLimit = 1 << (2 + (2 * (remainingBridges + 1)))
|
|
85
|
+
|
|
86
|
+
dictionaryStateToTotal: dict[int, int] = {}
|
|
87
|
+
arrayBitPattern = 1 if bridgesTotalIsOdd else ((1 << 2) | 1)
|
|
88
|
+
|
|
89
|
+
arrayPackedState = arrayBitPattern | arrayBitPattern << 1
|
|
90
|
+
while arrayPackedState < archStateLimit:
|
|
91
|
+
dictionaryStateToTotal[arrayPackedState] = 1
|
|
92
|
+
arrayBitPattern = ((arrayBitPattern << 2) | 1) << 2 | 1
|
|
93
|
+
arrayPackedState = arrayBitPattern | arrayBitPattern << 1
|
|
94
|
+
|
|
95
|
+
return dictionaryStateToTotal
|
|
96
|
+
|
|
97
|
+
def A005316(n: int) -> int:
|
|
98
|
+
return count(n, initializeA005316(n))
|
|
99
|
+
|
|
100
|
+
def A000682(n: int) -> int:
|
|
101
|
+
return count(n - 1, initializeA000682(n - 1))
|