mapFolding 0.7.1__py3-none-any.whl → 0.8.1__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 (48) hide show
  1. mapFolding/__init__.py +33 -4
  2. mapFolding/basecamp.py +14 -0
  3. mapFolding/beDRY.py +93 -82
  4. mapFolding/filesystem.py +124 -90
  5. mapFolding/noHomeYet.py +14 -2
  6. mapFolding/oeis.py +18 -3
  7. mapFolding/reference/flattened.py +46 -45
  8. mapFolding/reference/hunterNumba.py +4 -4
  9. mapFolding/reference/irvineJavaPort.py +1 -1
  10. mapFolding/reference/lunnanNumpy.py +3 -4
  11. mapFolding/reference/lunnanWhile.py +5 -7
  12. mapFolding/reference/rotatedEntryPoint.py +2 -3
  13. mapFolding/someAssemblyRequired/__init__.py +33 -3
  14. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +32 -15
  15. mapFolding/someAssemblyRequired/ingredientsNumba.py +108 -2
  16. mapFolding/someAssemblyRequired/synthesizeNumbaFlow.py +196 -0
  17. mapFolding/someAssemblyRequired/{synthesizeNumbaJob.py → synthesizeNumbaJobVESTIGIAL.py} +19 -23
  18. mapFolding/someAssemblyRequired/transformDataStructures.py +162 -0
  19. mapFolding/someAssemblyRequired/transformationTools.py +607 -252
  20. mapFolding/syntheticModules/numbaCount_doTheNeedful.py +197 -12
  21. mapFolding/theDao.py +37 -16
  22. mapFolding/theSSOT.py +47 -44
  23. {mapfolding-0.7.1.dist-info → mapfolding-0.8.1.dist-info}/METADATA +51 -46
  24. mapfolding-0.8.1.dist-info/RECORD +39 -0
  25. {mapfolding-0.7.1.dist-info → mapfolding-0.8.1.dist-info}/WHEEL +1 -1
  26. tests/conftest.py +2 -3
  27. tests/test_filesystem.py +0 -2
  28. tests/test_other.py +2 -3
  29. tests/test_tasks.py +0 -4
  30. mapFolding/reference/lunnan.py +0 -153
  31. mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -33
  32. mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +0 -7
  33. mapFolding/someAssemblyRequired/synthesizeDataConverters.py +0 -135
  34. mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -91
  35. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -91
  36. mapFolding/someAssemblyRequired/whatWillBe.py +0 -357
  37. mapFolding/syntheticModules/dataNamespaceFlattened.py +0 -30
  38. mapFolding/syntheticModules/multiprocessingCount_doTheNeedful.py +0 -216
  39. mapFolding/syntheticModules/numbaCount.py +0 -90
  40. mapFolding/syntheticModules/numbaCountExample.py +0 -158
  41. mapFolding/syntheticModules/numbaCountSequential.py +0 -111
  42. mapFolding/syntheticModules/numba_doTheNeedful.py +0 -12
  43. mapFolding/syntheticModules/numba_doTheNeedfulExample.py +0 -13
  44. mapfolding-0.7.1.dist-info/RECORD +0 -51
  45. /mapFolding/{syntheticModules → reference}/__init__.py +0 -0
  46. {mapfolding-0.7.1.dist-info → mapfolding-0.8.1.dist-info}/entry_points.txt +0 -0
  47. {mapfolding-0.7.1.dist-info → mapfolding-0.8.1.dist-info/licenses}/LICENSE +0 -0
  48. {mapfolding-0.7.1.dist-info → mapfolding-0.8.1.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py CHANGED
@@ -1,9 +1,38 @@
1
+ """
2
+ Map folding enumeration and counting algorithms with optimization capabilities.
3
+
4
+ This package implements algorithms to count and enumerate the various ways
5
+ a rectangular map can be folded, based on the mathematical problem described
6
+ in Lunnon's 1971 paper. It provides multiple layers of functionality, from
7
+ high-level user interfaces to low-level algorithmic optimizations and code
8
+ transformation tools.
9
+
10
+ Core modules:
11
+ - basecamp: Public API with simplified interfaces for end users
12
+ - theDao: Core computational algorithm using a functional state-transformation approach
13
+ - beDRY: Utility functions for common operations and parameter management
14
+ - theSSOT: Single Source of Truth for configuration, types, and state management
15
+ - oeis: Interface to the Online Encyclopedia of Integer Sequences for known results
16
+
17
+ Extended functionality:
18
+ - someAssemblyRequired: Code transformation framework that optimizes the core algorithm
19
+ through AST manipulation, dataclass transformation, and compilation techniques
20
+
21
+ Special directories:
22
+ - .cache/: Stores cached data from external sources like OEIS to improve performance
23
+ - syntheticModules/: Contains dynamically generated, optimized implementations of the
24
+ core algorithm created by the code transformation framework
25
+
26
+ This package strives to balance algorithm readability and understandability with
27
+ high-performance computation capabilities, allowing users to compute map folding
28
+ totals for larger dimensions than previously feasible.
29
+ """
1
30
  from mapFolding.basecamp import countFolds as countFolds
2
31
  from mapFolding.oeis import clearOEIScache, getOEISids, OEIS_for_n
3
32
 
4
33
  __all__ = [
5
- 'clearOEIScache',
6
- 'countFolds',
7
- 'getOEISids',
8
- 'OEIS_for_n',
34
+ 'clearOEIScache',
35
+ 'countFolds',
36
+ 'getOEISids',
37
+ 'OEIS_for_n',
9
38
  ]
mapFolding/basecamp.py CHANGED
@@ -1,3 +1,17 @@
1
+ """
2
+ Public API for the map folding algorithm with simplified interface.
3
+
4
+ This module provides the main entry point for users of the mapFolding package,
5
+ abstracting away the complexities of the computational algorithm. It offers
6
+ a high-level interface to count the total number of possible ways to fold
7
+ a rectangular map of specified dimensions, with options for customizing the
8
+ computation process and saving results.
9
+
10
+ The primary function is countFolds, which handles parameter validation,
11
+ computation state management, dispatching to the appropriate algorithm
12
+ implementation, and optional persistence of results.
13
+ """
14
+
1
15
  from collections.abc import Sequence
2
16
  from mapFolding.beDRY import outfitCountFolds, setCPUlimit, validateListDimensions
3
17
  from mapFolding.filesystem import getPathFilenameFoldsTotal, saveFoldsTotal
mapFolding/beDRY.py CHANGED
@@ -1,30 +1,26 @@
1
- """A relatively stable API for oft-needed functionality."""
2
- from mapFolding.theSSOT import (
3
- Array3D,
4
- ComputationState,
5
- getDatatypePackage,
6
- getNumpyDtypeDefault,
7
- )
1
+ """
2
+ Utility functions for maintaining DRY (Don't Repeat Yourself) principles in the mapFolding package.
3
+
4
+ This module provides a collection of helper functions that abstract common operations needed
5
+ throughout the package, preventing code duplication and ensuring consistency. The functions
6
+ manage core aspects of the computation process, including:
7
+
8
+ 1. Resource allocation and system limits management
9
+ 2. Data structure initialization and manipulation
10
+ 3. Parameter validation and interpretation
11
+ 4. Construction of specialized arrays and matrices for the folding algorithm
12
+
13
+ The functions in this module serve as a relatively stable API for other modules to use,
14
+ particularly for initializing computation state, validating inputs, and creating data
15
+ structures needed by the folding algorithms.
16
+ """
8
17
  from collections.abc import Sequence
18
+ from mapFolding.theSSOT import Array3D, ComputationState, getDatatypePackage, getNumpyDtypeDefault
9
19
  from sys import maxsize as sysMaxsize
10
20
  from typing import Any
11
21
  from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
12
22
  import numpy
13
23
 
14
- def validateListDimensions(listDimensions: Sequence[int]) -> tuple[int, ...]:
15
- if not listDimensions:
16
- raise ValueError("listDimensions is a required parameter.")
17
- listValidated: list[int] = intInnit(listDimensions, 'listDimensions')
18
- listNonNegative: list[int] = []
19
- for dimension in listValidated:
20
- if dimension < 0:
21
- raise ValueError(f"Dimension {dimension} must be non-negative")
22
- listNonNegative.append(dimension)
23
- dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
24
- if len(dimensionsValid) < 2:
25
- raise NotImplementedError(f"This function requires listDimensions, {listDimensions}, to have at least two dimensions greater than 0. You may want to look at https://oeis.org/.")
26
- return tuple(sorted(dimensionsValid))
27
-
28
24
  def getLeavesTotal(mapShape: tuple[int, ...]) -> int:
29
25
  productDimensions = 1
30
26
  for dimension in mapShape:
@@ -33,7 +29,59 @@ def getLeavesTotal(mapShape: tuple[int, ...]) -> int:
33
29
  productDimensions *= dimension
34
30
  return productDimensions
35
31
 
36
- def getNumpyDtype(datatype: type[numpy.signedinteger[Any]] | None = None) -> type[numpy.signedinteger[Any]]:
32
+ def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, leavesTotal: int) -> int:
33
+ """
34
+ Determines whether to divide the computation into tasks and how many divisions.
35
+
36
+ Parameters
37
+ ----------
38
+ computationDivisions (None)
39
+ Specifies how to divide computations:
40
+ - `None`: no division of the computation into tasks; sets task divisions to 0.
41
+ - int: direct set the number of task divisions; cannot exceed the map's total leaves.
42
+ - `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
43
+ - `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
44
+ concurrencyLimit
45
+ Maximum number of concurrent tasks allowed.
46
+ CPUlimit
47
+ for error reporting.
48
+ listDimensions
49
+ for error reporting.
50
+
51
+ Returns
52
+ -------
53
+ taskDivisions
54
+ How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
55
+
56
+ Raises
57
+ ------
58
+ ValueError
59
+ If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
60
+
61
+ Notes
62
+ -----
63
+ Task divisions should not exceed total leaves or the folds will be over-counted.
64
+ """
65
+ taskDivisions = 0
66
+ if not computationDivisions:
67
+ pass
68
+ elif isinstance(computationDivisions, int):
69
+ taskDivisions = computationDivisions
70
+ elif isinstance(computationDivisions, str): # type: ignore
71
+ # 'Unnecessary isinstance call; "str" is always an instance of "str", so sayeth Pylance'. Yeah, well "User is not always an instance of "correct input" so sayeth the programmer.
72
+ computationDivisions = computationDivisions.lower()
73
+ if computationDivisions == 'maximum':
74
+ taskDivisions = leavesTotal
75
+ elif computationDivisions == 'cpu':
76
+ taskDivisions = min(concurrencyLimit, leavesTotal)
77
+ else:
78
+ raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
79
+
80
+ if taskDivisions > leavesTotal:
81
+ raise ValueError(f"Problem: `taskDivisions`, ({taskDivisions}), is greater than `leavesTotal`, ({leavesTotal}), which will cause duplicate counting of the folds.\n\nChallenge: you cannot directly set `taskDivisions` or `leavesTotal`. They are derived from parameters that may or may not still be named `computationDivisions`, `CPUlimit` , and `listDimensions` and from dubious-quality Python code.")
82
+ return int(max(0, taskDivisions))
83
+
84
+ def interpretParameter_datatype(datatype: type[numpy.signedinteger[Any]] | None = None) -> type[numpy.signedinteger[Any]]:
37
85
  """An imperfect way to reduce code duplication."""
38
86
  if 'numpy' == getDatatypePackage():
39
87
  numpyDtype = datatype or getNumpyDtypeDefault()
@@ -42,7 +90,7 @@ def getNumpyDtype(datatype: type[numpy.signedinteger[Any]] | None = None) -> typ
42
90
  return numpyDtype
43
91
 
44
92
  def makeConnectionGraph(mapShape: tuple[int, ...], leavesTotal: int, datatype: type[numpy.signedinteger[Any]] | None = None) -> Array3D:
45
- numpyDtype = getNumpyDtype(datatype)
93
+ numpyDtype = interpretParameter_datatype(datatype)
46
94
  dimensionsTotal = len(mapShape)
47
95
  cumulativeProduct = numpy.multiply.accumulate([1] + list(mapShape), dtype=numpyDtype)
48
96
  arrayDimensions = numpy.array(mapShape, dtype=numpyDtype)
@@ -69,9 +117,15 @@ def makeConnectionGraph(mapShape: tuple[int, ...], leavesTotal: int, datatype: t
69
117
  return connectionGraph
70
118
 
71
119
  def makeDataContainer(shape: int | tuple[int, ...], datatype: type[numpy.signedinteger[Any]] | None = None) -> numpy.ndarray[Any, numpy.dtype[numpy.signedinteger[Any]]]:
72
- numpyDtype = getNumpyDtype(datatype)
120
+ numpyDtype = interpretParameter_datatype(datatype)
73
121
  return numpy.zeros(shape, dtype=numpyDtype)
74
122
 
123
+ def outfitCountFolds(mapShape: tuple[int, ...], computationDivisions: int | str | None = None, concurrencyLimit: int = 1) -> ComputationState:
124
+ leavesTotal = getLeavesTotal(mapShape)
125
+ taskDivisions = getTaskDivisions(computationDivisions, concurrencyLimit, leavesTotal)
126
+ computationStateInitialized = ComputationState(mapShape, leavesTotal, taskDivisions, concurrencyLimit)
127
+ return computationStateInitialized
128
+
75
129
  def setCPUlimit(CPUlimit: Any | None) -> int:
76
130
  """Sets CPU limit for concurrent operations.
77
131
 
@@ -106,66 +160,23 @@ def setCPUlimit(CPUlimit: Any | None) -> int:
106
160
  concurrencyLimit: int = defineConcurrencyLimit(CPUlimit, get_num_threads())
107
161
  set_num_threads(concurrencyLimit)
108
162
  concurrencyLimit = get_num_threads()
109
- elif concurrencyPackage == 'algorithm':
163
+ elif concurrencyPackage == 'multiprocessing':
110
164
  # When to use multiprocessing.set_start_method https://github.com/hunterhogan/mapFolding/issues/6
111
- concurrencyLimit: int = defineConcurrencyLimit(CPUlimit)
165
+ concurrencyLimit = defineConcurrencyLimit(CPUlimit)
112
166
  else:
113
167
  raise NotImplementedError(f"I received {concurrencyPackage=} but I don't know what to do with that.")
114
168
  return concurrencyLimit
115
169
 
116
- def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, leavesTotal: int) -> int:
117
- """
118
- Determines whether to divide the computation into tasks and how many divisions.
119
-
120
- Parameters
121
- ----------
122
- computationDivisions (None)
123
- Specifies how to divide computations:
124
- - `None`: no division of the computation into tasks; sets task divisions to 0.
125
- - int: direct set the number of task divisions; cannot exceed the map's total leaves.
126
- - `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
127
- - `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
128
- concurrencyLimit
129
- Maximum number of concurrent tasks allowed.
130
- CPUlimit
131
- for error reporting.
132
- listDimensions
133
- for error reporting.
134
-
135
- Returns
136
- -------
137
- taskDivisions
138
- How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
139
-
140
- Raises
141
- ------
142
- ValueError
143
- If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
144
-
145
- Notes
146
- -----
147
- Task divisions should not exceed total leaves or the folds will be over-counted.
148
- """
149
- taskDivisions = 0
150
- if not computationDivisions:
151
- pass
152
- elif isinstance(computationDivisions, int):
153
- taskDivisions = computationDivisions
154
- elif isinstance(computationDivisions, str): # type: ignore 'Unnecessary isinstance call; "str" is always an instance of "str", so sayeth Pylance'. Yeah, well "User is not always an instance of "correct input" so sayeth the programmer.
155
- computationDivisions = computationDivisions.lower()
156
- if computationDivisions == 'maximum':
157
- taskDivisions = leavesTotal
158
- elif computationDivisions == 'cpu':
159
- taskDivisions = min(concurrencyLimit, leavesTotal)
160
- else:
161
- raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
162
-
163
- if taskDivisions > leavesTotal:
164
- raise ValueError(f"Problem: `taskDivisions`, ({taskDivisions}), is greater than `leavesTotal`, ({leavesTotal}), which will cause duplicate counting of the folds.\n\nChallenge: you cannot directly set `taskDivisions` or `leavesTotal`. They are derived from parameters that may or may not still be named `computationDivisions`, `CPUlimit` , and `listDimensions` and from dubious-quality Python code.")
165
- return int(max(0, taskDivisions))
166
-
167
- def outfitCountFolds(mapShape: tuple[int, ...], computationDivisions: int | str | None = None, concurrencyLimit: int = 1) -> ComputationState:
168
- leavesTotal = getLeavesTotal(mapShape)
169
- taskDivisions = getTaskDivisions(computationDivisions, concurrencyLimit, leavesTotal)
170
- computationStateInitialized = ComputationState(mapShape, leavesTotal, taskDivisions, concurrencyLimit)
171
- return computationStateInitialized
170
+ def validateListDimensions(listDimensions: Sequence[int]) -> tuple[int, ...]:
171
+ if not listDimensions:
172
+ raise ValueError("listDimensions is a required parameter.")
173
+ listValidated: list[int] = intInnit(listDimensions, 'listDimensions')
174
+ listNonNegative: list[int] = []
175
+ for dimension in listValidated:
176
+ if dimension < 0:
177
+ raise ValueError(f"Dimension {dimension} must be non-negative")
178
+ listNonNegative.append(dimension)
179
+ dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
180
+ if len(dimensionsValid) < 2:
181
+ raise NotImplementedError(f"This function requires listDimensions, {listDimensions}, to have at least two dimensions greater than 0. You may want to look at https://oeis.org/.")
182
+ return tuple(sorted(dimensionsValid))
mapFolding/filesystem.py CHANGED
@@ -1,95 +1,129 @@
1
- """Filesystem functions for mapFolding package."""
1
+ """
2
+ Filesystem utilities for managing map folding computation results.
3
+
4
+ This module provides functions for standardized handling of files related to the mapFolding
5
+ package, with a focus on saving, retrieving, and naming computation results. It implements
6
+ consistent naming conventions and path resolution strategies to ensure that:
7
+
8
+ 1. Computation results are stored in a predictable location
9
+ 2. Filenames follow a consistent pattern based on map dimensions
10
+ 3. Results can be reliably retrieved for future reference
11
+ 4. The system handles file operations safely with appropriate error handling
12
+
13
+ The module serves as the interface between the computational components of the package
14
+ and the filesystem, abstracting away the details of file operations and path management.
15
+ """
2
16
  from pathlib import Path, PurePath
3
17
  from typing import Any
18
+ from os import PathLike
4
19
  import os
5
20
 
6
21
  def getFilenameFoldsTotal(mapShape: tuple[int, ...]) -> str:
7
- """Imagine your computer has been counting folds for 9 days, and when it tries to save your newly discovered value,
8
- the filename is invalid. I bet you think this function is more important after that thought experiment.
9
-
10
- Make a standardized filename for the computed value `foldsTotal`.
11
-
12
- The filename takes into account
13
- - the dimensions of the map, aka `mapShape`, aka `listDimensions`
14
- - no spaces in the filename
15
- - safe filesystem characters
16
- - unique extension
17
- - Python-safe strings:
18
- - no starting with a number
19
- - no reserved words
20
- - no dashes or other special characters
21
- - uh, I can't remember, but I found some other frustrating limitations
22
- - if 'p' is still the first character of the filename, I picked that because it was the original identifier for the map shape in Lunnan's code
23
-
24
- Parameters:
25
- mapShape: A sequence of integers representing the dimensions of the map.
26
-
27
- Returns:
28
- filenameFoldsTotal: A filename string in format 'pMxN.foldsTotal' where M,N are sorted dimensions
29
- """
30
- return 'p' + 'x'.join(str(dimension) for dimension in sorted(mapShape)) + '.foldsTotal'
31
-
32
- def getPathFilenameFoldsTotal(mapShape: tuple[int, ...], pathLikeWriteFoldsTotal: str | os.PathLike[str] | None = None) -> Path:
33
- """Get a standardized path and filename for the computed value `foldsTotal`.
34
-
35
- If you provide a directory, the function will append a standardized filename. If you provide a filename
36
- or a relative path and filename, the function will prepend the default path.
37
-
38
- Parameters:
39
- mapShape: List of dimensions for the map folding problem.
40
- pathLikeWriteFoldsTotal (pathJobRootDEFAULT): Path, filename, or relative path and filename. If None, uses default path.
41
- Defaults to None.
42
-
43
- Returns:
44
- pathFilenameFoldsTotal: Absolute path and filename.
45
- """
46
- from mapFolding.theSSOT import getPathJobRootDEFAULT
47
-
48
- if pathLikeWriteFoldsTotal is None:
49
- pathFilenameFoldsTotal = getPathJobRootDEFAULT() / getFilenameFoldsTotal(mapShape)
50
- else:
51
- pathLikeSherpa = Path(pathLikeWriteFoldsTotal)
52
- if pathLikeSherpa.is_dir():
53
- pathFilenameFoldsTotal = pathLikeSherpa / getFilenameFoldsTotal(mapShape)
54
- elif pathLikeSherpa.is_file() and pathLikeSherpa.is_absolute():
55
- pathFilenameFoldsTotal = pathLikeSherpa
56
- else:
57
- pathFilenameFoldsTotal = getPathJobRootDEFAULT() / pathLikeSherpa
58
-
59
- pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
60
- return pathFilenameFoldsTotal
61
-
62
- def saveFoldsTotal(pathFilename: str | os.PathLike[str], foldsTotal: int) -> None:
63
- """
64
- Save foldsTotal with multiple fallback mechanisms.
65
-
66
- Parameters:
67
- pathFilename: Target save location
68
- foldsTotal: Critical computed value to save
69
- """
70
- try:
71
- pathFilenameFoldsTotal = Path(pathFilename)
72
- pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
73
- pathFilenameFoldsTotal.write_text(str(foldsTotal))
74
- except Exception as ERRORmessage:
75
- try:
76
- print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
77
- print(ERRORmessage)
78
- print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
79
- randomnessPlanB = (int(str(foldsTotal).strip()[-1]) + 1) * ['YO_']
80
- filenameInfixUnique = ''.join(randomnessPlanB)
81
- pathFilenamePlanB = os.path.join(os.getcwd(), 'foldsTotal' + filenameInfixUnique + '.txt')
82
- writeStreamFallback = open(pathFilenamePlanB, 'w')
83
- writeStreamFallback.write(str(foldsTotal))
84
- writeStreamFallback.close()
85
- print(str(pathFilenamePlanB))
86
- except Exception:
87
- print(foldsTotal)
88
- return None
89
-
90
- def writeStringToHere(this: str, pathFilename: str | os.PathLike[Any] | PurePath) -> None:
91
- """Write the string `this` to the file at `pathFilename`."""
92
- pathFilename = Path(pathFilename)
93
- pathFilename.parent.mkdir(parents=True, exist_ok=True)
94
- pathFilename.write_text(str(this))
95
- return None
22
+ """
23
+ Create a standardized filename for a computed `foldsTotal` value.
24
+
25
+ This function generates a consistent, filesystem-safe filename based on map dimensions.
26
+ Standardizing filenames ensures that results can be reliably stored and retrieved,
27
+ avoiding potential filesystem incompatibilities or Python naming restrictions.
28
+
29
+ Parameters:
30
+ mapShape: A sequence of integers representing the dimensions of the map.
31
+
32
+ Returns:
33
+ filenameFoldsTotal: A filename string in format 'pMxN.foldsTotal' where M,N are sorted dimensions.
34
+
35
+ Notes:
36
+ The filename format ensures:
37
+ - No spaces in the filename
38
+ - Safe filesystem characters
39
+ - Unique extension (.foldsTotal)
40
+ - Python-safe strings (no starting with numbers, no reserved words)
41
+ - The 'p' prefix preserves compatibility with Lunnan's original code.
42
+ """
43
+ return 'p' + 'x'.join(str(dimension) for dimension in sorted(mapShape)) + '.foldsTotal'
44
+
45
+ def getPathFilenameFoldsTotal(mapShape: tuple[int, ...], pathLikeWriteFoldsTotal: str | PathLike[str] | None = None) -> Path:
46
+ """
47
+ Get a standardized path and filename for the computed foldsTotal value.
48
+
49
+ This function resolves paths for storing computation results, handling different
50
+ input types including directories, absolute paths, or relative paths. It ensures
51
+ that all parent directories exist in the resulting path.
52
+
53
+ Parameters:
54
+ mapShape: List of dimensions for the map folding problem.
55
+ pathLikeWriteFoldsTotal (getPathJobRootDEFAULT): Path, filename, or relative path and filename.
56
+ If None, uses default path. If a directory, appends standardized filename.
57
+
58
+ Returns:
59
+ pathFilenameFoldsTotal: Absolute path and filename for storing the foldsTotal value.
60
+
61
+ Notes:
62
+ The function creates any necessary directories in the path if they don't exist.
63
+ """
64
+ from mapFolding.theSSOT import getPathJobRootDEFAULT
65
+
66
+ if pathLikeWriteFoldsTotal is None:
67
+ pathFilenameFoldsTotal = getPathJobRootDEFAULT() / getFilenameFoldsTotal(mapShape)
68
+ else:
69
+ pathLikeSherpa = Path(pathLikeWriteFoldsTotal)
70
+ if pathLikeSherpa.is_dir():
71
+ pathFilenameFoldsTotal = pathLikeSherpa / getFilenameFoldsTotal(mapShape)
72
+ elif pathLikeSherpa.is_file() and pathLikeSherpa.is_absolute():
73
+ pathFilenameFoldsTotal = pathLikeSherpa
74
+ else:
75
+ pathFilenameFoldsTotal = getPathJobRootDEFAULT() / pathLikeSherpa
76
+
77
+ pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
78
+ return pathFilenameFoldsTotal
79
+
80
+ def saveFoldsTotal(pathFilename: str | PathLike[str], foldsTotal: int) -> None:
81
+ """
82
+ Save `foldsTotal` value to disk with multiple fallback mechanisms.
83
+
84
+ This function attempts to save the computed `foldsTotal` value to the specified
85
+ location, with backup strategies in case the primary save attempt fails.
86
+ The robustness is critical since these computations may take days to complete.
87
+
88
+ Parameters:
89
+ pathFilename: Target save location for the `foldsTotal` value
90
+ foldsTotal: The computed value to save
91
+
92
+ Notes:
93
+ If the primary save fails, the function will attempt alternative save methods:
94
+ 1. Print the value prominently to stdout
95
+ 2. Create a fallback file in the current working directory
96
+ 3. As a last resort, simply print the value
97
+ """
98
+ try:
99
+ pathFilenameFoldsTotal = Path(pathFilename)
100
+ pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
101
+ pathFilenameFoldsTotal.write_text(str(foldsTotal))
102
+ except Exception as ERRORmessage:
103
+ try:
104
+ print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
105
+ print(ERRORmessage)
106
+ print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
107
+ randomnessPlanB = (int(str(foldsTotal).strip()[-1]) + 1) * ['YO_']
108
+ filenameInfixUnique = ''.join(randomnessPlanB)
109
+ pathFilenamePlanB = os.path.join(os.getcwd(), 'foldsTotal' + filenameInfixUnique + '.txt')
110
+ writeStreamFallback = open(pathFilenamePlanB, 'w')
111
+ writeStreamFallback.write(str(foldsTotal))
112
+ writeStreamFallback.close()
113
+ print(str(pathFilenamePlanB))
114
+ except Exception:
115
+ print(foldsTotal)
116
+ return None
117
+
118
+ def writeStringToHere(this: str, pathFilename: str | PathLike[Any] | PurePath) -> None:
119
+ """
120
+ Write a string to a file, creating parent directories if needed.
121
+
122
+ Parameters:
123
+ this: The string content to write to the file
124
+ pathFilename: The target file path where the string should be written
125
+ """
126
+ pathFilename = Path(pathFilename)
127
+ pathFilename.parent.mkdir(parents=True, exist_ok=True)
128
+ pathFilename.write_text(str(this))
129
+ return None
mapFolding/noHomeYet.py CHANGED
@@ -1,3 +1,15 @@
1
+ """
2
+ Interface for retrieving known map folding totals from OEIS (Online Encyclopedia of Integer Sequences).
3
+
4
+ This module provides utilities for accessing pre-computed map folding totals that are known
5
+ from mathematical literature and stored in the OEIS. The functions cache results for
6
+ performance and provide lookups based on map dimensions.
7
+
8
+ The main functions are:
9
+ - makeDictionaryFoldsTotalKnown: Creates a dictionary of known folding totals indexed by map dimensions
10
+ - getFoldsTotalKnown: Retrieves the folding total for a specific map shape, returning -1 if unknown
11
+ """
12
+
1
13
  from functools import cache
2
14
  from mapFolding.oeis import settingsOEIS
3
15
 
@@ -11,8 +23,8 @@ def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
11
23
 
12
24
  for n, foldingsTotal in sequence.items():
13
25
  mapShape = settings['getMapShape'](n)
14
- mapShape = sorted(mapShape)
15
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(mapShape)] = foldingsTotal
26
+ mapShape = tuple(sorted(mapShape))
27
+ dictionaryMapDimensionsToFoldsTotalKnown[mapShape] = foldingsTotal
16
28
  return dictionaryMapDimensionsToFoldsTotalKnown
17
29
 
18
30
  def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
mapFolding/oeis.py CHANGED
@@ -1,4 +1,19 @@
1
- """Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS); _only_ things that implement _only_ the OEIS."""
1
+ """
2
+ Interface to The Online Encyclopedia of Integer Sequences (OEIS) for map folding sequences.
3
+
4
+ This module provides a comprehensive interface for accessing and utilizing integer sequences
5
+ from the OEIS that relate to map folding problems. It implements functionality to:
6
+
7
+ 1. Retrieve sequence data from OEIS with local caching for performance
8
+ 2. Map sequence indices to corresponding map shapes based on sequence definitions
9
+ 3. Provide a command-line interface for sequence lookups
10
+ 4. Execute map folding computations for sequence terms not available in OEIS
11
+
12
+ The module maintains a registry of implemented OEIS sequences (A001415-A001418, A195646)
13
+ with their metadata, known values, and functions to convert between sequence indices and
14
+ map dimensions. This allows the package to validate results against established mathematical
15
+ literature and extend sequences beyond their currently known terms.
16
+ """
2
17
  from collections.abc import Callable
3
18
  from datetime import datetime, timedelta
4
19
  from mapFolding.theSSOT import thePathPackage
@@ -144,10 +159,10 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
144
159
  return OEISsequence
145
160
 
146
161
  def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
147
- tryCache = False
162
+ tryCache: bool = False
148
163
  if pathFilenameCache.exists():
149
164
  fileAge: timedelta = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
150
- tryCache: bool = fileAge < timedelta(days=cacheDays)
165
+ tryCache = fileAge < timedelta(days=cacheDays)
151
166
 
152
167
  oeisInformation: str | None = None
153
168
  if tryCache: