mapFolding 0.13.0__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.
Files changed (43) hide show
  1. mapFolding/_oeisFormulas/A000136.py +4 -0
  2. mapFolding/_oeisFormulas/A000560.py +4 -0
  3. mapFolding/_oeisFormulas/A000682.py +17 -0
  4. mapFolding/_oeisFormulas/A005315.py +4 -0
  5. mapFolding/_oeisFormulas/A005316.py +10 -0
  6. mapFolding/_oeisFormulas/A223094.py +7 -0
  7. mapFolding/_oeisFormulas/A259702.py +4 -0
  8. mapFolding/_oeisFormulas/A301620.py +6 -0
  9. mapFolding/_oeisFormulas/Z0Z_aOFn.py +19 -0
  10. mapFolding/_oeisFormulas/Z0Z_oeisMeanders.py +53 -0
  11. mapFolding/_oeisFormulas/__init__.py +1 -0
  12. mapFolding/_oeisFormulas/matrixMeanders.py +65 -0
  13. mapFolding/_oeisFormulas/matrixMeandersAnnex.py +84 -0
  14. mapFolding/_theSSOT.py +68 -26
  15. mapFolding/basecamp.py +2 -2
  16. mapFolding/beDRY.py +1 -1
  17. mapFolding/oeis.py +56 -167
  18. mapFolding/reference/A005316JavaPort.py +134 -0
  19. mapFolding/reference/A005316imperative.py +101 -0
  20. mapFolding/reference/A005316intOptimized.py +122 -0
  21. mapFolding/reference/A005316optimized128bit.py +79 -0
  22. mapFolding/reference/A005316primitiveOptimized.py +97 -0
  23. mapFolding/reference/A005316redis.py +118 -0
  24. mapFolding/reference/A005316write2disk.py +169 -0
  25. mapFolding/reference/irvineJavaPort.py +4 -8
  26. mapFolding/reference/matrixMeandersBaseline.py +65 -0
  27. mapFolding/reference/matrixMeandersBaselineAnnex.py +84 -0
  28. mapFolding/someAssemblyRequired/RecipeJob.py +3 -4
  29. mapFolding/someAssemblyRequired/_toolkitContainers.py +3 -3
  30. mapFolding/someAssemblyRequired/makeAllModules.py +2 -2
  31. mapFolding/someAssemblyRequired/makeJobTheorem2codon.py +109 -141
  32. mapFolding/someAssemblyRequired/toolkitNumba.py +9 -7
  33. mapFolding/tests/conftest.py +13 -13
  34. mapFolding/tests/test_computations.py +4 -4
  35. mapFolding/tests/test_oeis.py +7 -14
  36. mapfolding-0.14.0.dist-info/METADATA +78 -0
  37. mapfolding-0.14.0.dist-info/RECORD +76 -0
  38. mapfolding-0.13.0.dist-info/METADATA +0 -154
  39. mapfolding-0.13.0.dist-info/RECORD +0 -54
  40. {mapfolding-0.13.0.dist-info → mapfolding-0.14.0.dist-info}/WHEEL +0 -0
  41. {mapfolding-0.13.0.dist-info → mapfolding-0.14.0.dist-info}/entry_points.txt +0 -0
  42. {mapfolding-0.13.0.dist-info → mapfolding-0.14.0.dist-info}/licenses/LICENSE +0 -0
  43. {mapfolding-0.13.0.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 mapFolding import countFolds, packageSettings
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
- cacheDays = 30
42
- """Number of days to retain cached OEIS data before refreshing from the online source."""
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
- class SettingsOEIShardcodedValues(TypedDict):
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 validateOEISid(oeisIDcandidate: str) -> str:
131
- """Validate an OEIS sequence ID against implemented sequences.
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
- oeisIDcandidate : str
141
- The OEIS sequence identifier to validate.
69
+ oeisID : str
70
+ The OEIS sequence identifier to standardize.
142
71
 
143
72
  Returns
144
73
  -------
145
- str
146
- The validated and possibly modified OEIS sequence ID, if recognized.
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
- if oeisIDcandidate in oeisIDsImplemented:
155
- return oeisIDcandidate
156
- else:
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 = validateOEISid(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 getOEISofficial(pathFilenameCache: Path, url: str) -> None | str:
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
- raise ValueError("URL must start with 'http:' or 'https:'")
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 / getFilenameOEISbFile(oeisID)
306
- url: str = f"https://oeis.org/{oeisID}/{getFilenameOEISbFile(oeisID)}"
217
+ pathFilenameCache: Path = pathCache / _getFilenameOEISbFile(oeisID)
218
+ url: str = f"https://oeis.org/{oeisID}/{_getFilenameOEISbFile(oeisID)}"
307
219
 
308
- oeisInformation: None | str = getOEISofficial(pathFilenameCache, url)
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 = validateOEISid(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 = getOEISofficial(pathFilenameCache, url)
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: # noqa: PLR2004
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 makeSettingsOEIS() -> dict[str, SettingsOEIS]:
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
- settingsTarget: dict[str, SettingsOEIS] = {}
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
- settingsTarget[oeisID] = SettingsOEIS(
309
+ dictionaryOEIS[oeisID] = MetadataOEISid(
398
310
  description=descriptionSherpa,
399
311
  offset=offsetSherpa,
400
- getMapShape=settingsOEIShardcodedValues[oeisID]['getMapShape'],
401
- valuesBenchmark=settingsOEIShardcodedValues[oeisID]['valuesBenchmark'],
402
- valuesTestParallelization=settingsOEIShardcodedValues[oeisID]['valuesTestParallelization'],
403
- valuesTestValidation=settingsOEIShardcodedValues[oeisID]['valuesTestValidation'] + list(range(offsetSherpa, 2)),
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 settingsTarget
319
+ return dictionaryOEIS
408
320
 
409
- settingsOEIS: dict[str, SettingsOEIS] = makeSettingsOEIS()
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
- """Create a cached lookup dictionary mapping map shapes to their known folding totals.
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
- dictionaryMapDimensionsToFoldsTotalKnown : dict[tuple[int, ...], int]
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
- dictionaryMapDimensionsToFoldsTotalKnown: dict[tuple[int, ...], int] = {}
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 = settingsOEIS[exampleOEISid]['valuesTestValidation'][-1]
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}: {settingsOEIS[oeisID]['description']}"
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 = validateOEISid(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, ...] = settingsOEIS[oeisID]['getMapShape'](n)
451
+ mapShape: tuple[int, ...] = dictionaryOEIS[oeisID]['getMapShape'](n)
563
452
 
564
- if n <= 1 or len(mapShape) < 2: # noqa: PLR2004
565
- offset: int = settingsOEIS[oeisID]['offset']
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 = settingsOEIS[oeisID]['valuesKnown'][n]
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 settingsOEIS:
522
+ for oeisID in dictionaryOEIS:
634
523
  ( pathCache / f"{oeisID}.txt" ).unlink(missing_ok=True)
635
- ( pathCache / getFilenameOEISbFile(oeisID) ).unlink(missing_ok=True)
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))