mapFolding 0.7.0__tar.gz → 0.8.0__tar.gz

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 (60) hide show
  1. {mapfolding-0.7.0 → mapfolding-0.8.0}/PKG-INFO +6 -7
  2. {mapfolding-0.7.0 → mapfolding-0.8.0}/README.md +4 -4
  3. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/__init__.py +1 -1
  4. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/basecamp.py +2 -2
  5. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/beDRY.py +88 -85
  6. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/filesystem.py +37 -29
  7. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/noHomeYet.py +2 -2
  8. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/oeis.py +2 -2
  9. mapfolding-0.8.0/mapFolding/someAssemblyRequired/Z0Z_workbench.py +350 -0
  10. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/someAssemblyRequired/__init__.py +4 -3
  11. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/someAssemblyRequired/getLLVMforNoReason.py +0 -1
  12. mapfolding-0.8.0/mapFolding/someAssemblyRequired/ingredientsNumba.py +185 -0
  13. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/someAssemblyRequired/synthesizeDataConverters.py +34 -52
  14. mapfolding-0.7.0/mapFolding/someAssemblyRequired/synthesizeNumbaJob.py → mapfolding-0.8.0/mapFolding/someAssemblyRequired/synthesizeNumbaJobVESTIGIAL.py +18 -21
  15. mapfolding-0.8.0/mapFolding/someAssemblyRequired/transformationTools.py +763 -0
  16. mapfolding-0.8.0/mapFolding/syntheticModules/numbaCount_doTheNeedful.py +198 -0
  17. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/theDao.py +57 -39
  18. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/theSSOT.py +59 -59
  19. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding.egg-info/PKG-INFO +6 -7
  20. mapfolding-0.8.0/mapFolding.egg-info/SOURCES.txt +44 -0
  21. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding.egg-info/requires.txt +1 -2
  22. {mapfolding-0.7.0 → mapfolding-0.8.0}/pyproject.toml +15 -14
  23. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/conftest.py +2 -3
  24. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/test_computations.py +9 -5
  25. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/test_filesystem.py +0 -2
  26. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/test_other.py +2 -3
  27. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/test_tasks.py +7 -5
  28. mapfolding-0.7.0/mapFolding/someAssemblyRequired/Z0Z_workbench.py +0 -34
  29. mapfolding-0.7.0/mapFolding/someAssemblyRequired/ingredientsNumba.py +0 -100
  30. mapfolding-0.7.0/mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +0 -7
  31. mapfolding-0.7.0/mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -91
  32. mapfolding-0.7.0/mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -91
  33. mapfolding-0.7.0/mapFolding/someAssemblyRequired/transformationTools.py +0 -425
  34. mapfolding-0.7.0/mapFolding/someAssemblyRequired/whatWillBe.py +0 -311
  35. mapfolding-0.7.0/mapFolding/syntheticModules/dataNamespaceFlattened.py +0 -30
  36. mapfolding-0.7.0/mapFolding/syntheticModules/numbaCount.py +0 -90
  37. mapfolding-0.7.0/mapFolding/syntheticModules/numbaCountSequential.py +0 -110
  38. mapfolding-0.7.0/mapFolding/syntheticModules/numba_doTheNeedful.py +0 -12
  39. mapfolding-0.7.0/mapFolding/syntheticModules/numba_doTheNeedfulExample.py +0 -13
  40. mapfolding-0.7.0/mapFolding.egg-info/SOURCES.txt +0 -97
  41. mapfolding-0.7.0/tests/__init__.py +0 -0
  42. {mapfolding-0.7.0 → mapfolding-0.8.0}/LICENSE +0 -0
  43. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/py.typed +0 -0
  44. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/flattened.py +0 -0
  45. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/hunterNumba.py +0 -0
  46. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/irvineJavaPort.py +0 -0
  47. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/jax.py +0 -0
  48. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/lunnan.py +0 -0
  49. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/lunnanNumpy.py +0 -0
  50. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/lunnanWhile.py +0 -0
  51. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/rotatedEntryPoint.py +0 -0
  52. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding/reference/total_countPlus1vsPlusN.py +0 -0
  53. /mapfolding-0.7.0/mapFolding/syntheticModules/numbaCountExample.py → /mapfolding-0.8.0/mapFolding/syntheticModules/numbaCountHistoricalExample.py +0 -0
  54. /mapfolding-0.7.0/mapFolding/syntheticModules/numbaCount_doTheNeedful.py → /mapfolding-0.8.0/mapFolding/syntheticModules/numba_doTheNeedfulHistoricalExample.py +0 -0
  55. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding.egg-info/dependency_links.txt +0 -0
  56. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding.egg-info/entry_points.txt +0 -0
  57. {mapfolding-0.7.0 → mapfolding-0.8.0}/mapFolding.egg-info/top_level.txt +0 -0
  58. {mapfolding-0.7.0 → mapfolding-0.8.0}/setup.cfg +0 -0
  59. {mapfolding-0.7.0/mapFolding/syntheticModules → mapfolding-0.8.0/tests}/__init__.py +0 -0
  60. {mapfolding-0.7.0 → mapfolding-0.8.0}/tests/test_oeis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mapFolding
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: Count distinct ways to fold a map (or a strip of stamps)
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
@@ -38,12 +38,11 @@ Requires-Dist: tomli
38
38
  Requires-Dist: Z0Z_tools
39
39
  Provides-Extra: testing
40
40
  Requires-Dist: mypy; extra == "testing"
41
+ Requires-Dist: pytest; extra == "testing"
41
42
  Requires-Dist: pytest-cov; extra == "testing"
42
43
  Requires-Dist: pytest-env; extra == "testing"
43
44
  Requires-Dist: pytest-xdist; extra == "testing"
44
- Requires-Dist: pytest; extra == "testing"
45
45
  Requires-Dist: pyupgrade; extra == "testing"
46
- Requires-Dist: updateCitation; extra == "testing"
47
46
 
48
47
  # mapFolding: Algorithms for enumerating distinct map/stamp folding patterns 🗺️
49
48
 
@@ -113,14 +112,14 @@ Available OEIS sequences:
113
112
 
114
113
  ### 4. **Customizing your algorithm**
115
114
 
116
- - mapFolding\someAssemblyRequired\synthesizeNumbaJob.py (and/or synthesizeNumba____.py, as applicable)
115
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\synthesizeNumbaJob.py (and/or synthesizeNumba____.py, as applicable)~~
117
116
  - Synthesize a Numba-optimized module for a specific mapShape
118
117
  - Synthesize _from_ a module in mapFolding\syntheticModules or from any source you select
119
118
  - Use the existing transformation options
120
119
  - Or create new ways of transforming the algorithm from its source to a specific job
121
- - mapFolding\someAssemblyRequired\makeJob.py
120
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\makeJob.py~~
122
121
  - Initialize data for a specific mapShape
123
- - mapFolding\someAssemblyRequired\synthesizeNumbaModules.py (and/or synthesizeNumba____.py, as applicable)
122
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\synthesizeNumbaModules.py (and/or synthesizeNumba____.py, as applicable)~~
124
123
  - Synthesize one or more Numba-optimized modules for parallel or sequential computation
125
124
  - Overwrite the modules in mapFolding\syntheticModules or save the module(s) to a custom path
126
125
  - Synthesize _from_ the algorithm(s) in mapFolding\theDao.py or from any source you select
@@ -131,7 +130,7 @@ Available OEIS sequences:
131
130
  - Modify the algorithms for initializing values, parallel computation, and/or sequential computation
132
131
  - Use the modified algorithm(s) in synthesizeNumbaModules.py, above, to create Numba-optimized version(s)
133
132
  - Then use a Numba-optimized version in synthesizeNumbaJob.py, above, to create a hyper-optimized version for a specific mapShape
134
- - mapFolding\theSSOT.py (and/or theSSOTnumba.py and/ or theSSOT____.py, if they exist)
133
+ - mapFolding\theSSOT.py
135
134
  - Modify broad settings or find functions to modify broad settings, such as data structures and their data types
136
135
  - Create new settings or groups of settings
137
136
  - mapFolding\beDRY.py
@@ -66,14 +66,14 @@ Available OEIS sequences:
66
66
 
67
67
  ### 4. **Customizing your algorithm**
68
68
 
69
- - mapFolding\someAssemblyRequired\synthesizeNumbaJob.py (and/or synthesizeNumba____.py, as applicable)
69
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\synthesizeNumbaJob.py (and/or synthesizeNumba____.py, as applicable)~~
70
70
  - Synthesize a Numba-optimized module for a specific mapShape
71
71
  - Synthesize _from_ a module in mapFolding\syntheticModules or from any source you select
72
72
  - Use the existing transformation options
73
73
  - Or create new ways of transforming the algorithm from its source to a specific job
74
- - mapFolding\someAssemblyRequired\makeJob.py
74
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\makeJob.py~~
75
75
  - Initialize data for a specific mapShape
76
- - mapFolding\someAssemblyRequired\synthesizeNumbaModules.py (and/or synthesizeNumba____.py, as applicable)
76
+ - Renovations in progress: ~~mapFolding\someAssemblyRequired\synthesizeNumbaModules.py (and/or synthesizeNumba____.py, as applicable)~~
77
77
  - Synthesize one or more Numba-optimized modules for parallel or sequential computation
78
78
  - Overwrite the modules in mapFolding\syntheticModules or save the module(s) to a custom path
79
79
  - Synthesize _from_ the algorithm(s) in mapFolding\theDao.py or from any source you select
@@ -84,7 +84,7 @@ Available OEIS sequences:
84
84
  - Modify the algorithms for initializing values, parallel computation, and/or sequential computation
85
85
  - Use the modified algorithm(s) in synthesizeNumbaModules.py, above, to create Numba-optimized version(s)
86
86
  - Then use a Numba-optimized version in synthesizeNumbaJob.py, above, to create a hyper-optimized version for a specific mapShape
87
- - mapFolding\theSSOT.py (and/or theSSOTnumba.py and/ or theSSOT____.py, if they exist)
87
+ - mapFolding\theSSOT.py
88
88
  - Modify broad settings or find functions to modify broad settings, such as data structures and their data types
89
89
  - Create new settings or groups of settings
90
90
  - mapFolding\beDRY.py
@@ -1,4 +1,4 @@
1
- from mapFolding.basecamp import countFolds
1
+ from mapFolding.basecamp import countFolds as countFolds
2
2
  from mapFolding.oeis import clearOEIScache, getOEISids, OEIS_for_n
3
3
 
4
4
  __all__ = [
@@ -43,8 +43,8 @@ def countFolds(listDimensions: Sequence[int]
43
43
  concurrencyLimit: int = setCPUlimit(CPUlimit)
44
44
  computationStateInitialized: ComputationState = outfitCountFolds(mapShape, computationDivisions, concurrencyLimit)
45
45
 
46
- dispatcher = getPackageDispatcher()
47
- computationStateComplete: ComputationState = dispatcher(computationStateInitialized)
46
+ dispatcherCallableProxy = getPackageDispatcher()
47
+ computationStateComplete: ComputationState = dispatcherCallableProxy(computationStateInitialized)
48
48
 
49
49
  computationStateComplete.getFoldsTotal()
50
50
 
@@ -1,30 +1,11 @@
1
1
  """A relatively stable API for oft-needed functionality."""
2
- from mapFolding.theSSOT import (
3
- Array3D,
4
- ComputationState,
5
- getDatatypePackage,
6
- getNumpyDtypeDefault,
7
- )
8
2
  from collections.abc import Sequence
3
+ from mapFolding.theSSOT import Array3D, ComputationState, getDatatypePackage, getNumpyDtypeDefault
9
4
  from sys import maxsize as sysMaxsize
10
5
  from typing import Any
11
6
  from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
12
7
  import numpy
13
8
 
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
9
  def getLeavesTotal(mapShape: tuple[int, ...]) -> int:
29
10
  productDimensions = 1
30
11
  for dimension in mapShape:
@@ -33,7 +14,59 @@ def getLeavesTotal(mapShape: tuple[int, ...]) -> int:
33
14
  productDimensions *= dimension
34
15
  return productDimensions
35
16
 
36
- def getNumpyDtype(datatype: type[numpy.signedinteger[Any]] | None = None) -> type[numpy.signedinteger[Any]]:
17
+ def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, leavesTotal: int) -> int:
18
+ """
19
+ Determines whether to divide the computation into tasks and how many divisions.
20
+
21
+ Parameters
22
+ ----------
23
+ computationDivisions (None)
24
+ Specifies how to divide computations:
25
+ - `None`: no division of the computation into tasks; sets task divisions to 0.
26
+ - int: direct set the number of task divisions; cannot exceed the map's total leaves.
27
+ - `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
28
+ - `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
29
+ concurrencyLimit
30
+ Maximum number of concurrent tasks allowed.
31
+ CPUlimit
32
+ for error reporting.
33
+ listDimensions
34
+ for error reporting.
35
+
36
+ Returns
37
+ -------
38
+ taskDivisions
39
+ How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
40
+
41
+ Raises
42
+ ------
43
+ ValueError
44
+ If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
45
+
46
+ Notes
47
+ -----
48
+ Task divisions should not exceed total leaves or the folds will be over-counted.
49
+ """
50
+ taskDivisions = 0
51
+ if not computationDivisions:
52
+ pass
53
+ elif isinstance(computationDivisions, int):
54
+ taskDivisions = computationDivisions
55
+ elif isinstance(computationDivisions, str): # type: ignore
56
+ # '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.
57
+ computationDivisions = computationDivisions.lower()
58
+ if computationDivisions == 'maximum':
59
+ taskDivisions = leavesTotal
60
+ elif computationDivisions == 'cpu':
61
+ taskDivisions = min(concurrencyLimit, leavesTotal)
62
+ else:
63
+ raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
64
+
65
+ if taskDivisions > leavesTotal:
66
+ 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.")
67
+ return int(max(0, taskDivisions))
68
+
69
+ def interpretParameter_datatype(datatype: type[numpy.signedinteger[Any]] | None = None) -> type[numpy.signedinteger[Any]]:
37
70
  """An imperfect way to reduce code duplication."""
38
71
  if 'numpy' == getDatatypePackage():
39
72
  numpyDtype = datatype or getNumpyDtypeDefault()
@@ -42,7 +75,7 @@ def getNumpyDtype(datatype: type[numpy.signedinteger[Any]] | None = None) -> typ
42
75
  return numpyDtype
43
76
 
44
77
  def makeConnectionGraph(mapShape: tuple[int, ...], leavesTotal: int, datatype: type[numpy.signedinteger[Any]] | None = None) -> Array3D:
45
- numpyDtype = getNumpyDtype(datatype)
78
+ numpyDtype = interpretParameter_datatype(datatype)
46
79
  dimensionsTotal = len(mapShape)
47
80
  cumulativeProduct = numpy.multiply.accumulate([1] + list(mapShape), dtype=numpyDtype)
48
81
  arrayDimensions = numpy.array(mapShape, dtype=numpyDtype)
@@ -69,11 +102,24 @@ def makeConnectionGraph(mapShape: tuple[int, ...], leavesTotal: int, datatype: t
69
102
  return connectionGraph
70
103
 
71
104
  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)
105
+ numpyDtype = interpretParameter_datatype(datatype)
73
106
  return numpy.zeros(shape, dtype=numpyDtype)
74
107
 
108
+ def outfitCountFolds(mapShape: tuple[int, ...], computationDivisions: int | str | None = None, concurrencyLimit: int = 1) -> ComputationState:
109
+ leavesTotal = getLeavesTotal(mapShape)
110
+ taskDivisions = getTaskDivisions(computationDivisions, concurrencyLimit, leavesTotal)
111
+ computationStateInitialized = ComputationState(mapShape, leavesTotal, taskDivisions, concurrencyLimit)
112
+ return computationStateInitialized
113
+
75
114
  def setCPUlimit(CPUlimit: Any | None) -> int:
76
- """Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
115
+ """Sets CPU limit for concurrent operations.
116
+
117
+ If the concurrency is managed by `numba`, the maximum number of CPUs is retrieved from `numba.get_num_threads()` and not by polling the hardware. Therefore, if there are
118
+ numba environment variables limiting the number of available CPUs, that will effect this function. That _should_ be a good thing: you control the number of CPUs available
119
+ to numba. But if you're not aware of that, you might be surprised by the results.
120
+
121
+ If you are designing custom modules that use numba, note that you must call `numba.set_num_threads()` (i.e., this function) before executing an `import` statement
122
+ on a Numba-jitted function. Otherwise, the `numba.set_num_threads()` call will have no effect on the imported function.
77
123
 
78
124
  Parameters:
79
125
  CPUlimit: whether and how to limit the CPU usage. See notes for details.
@@ -93,72 +139,29 @@ def setCPUlimit(CPUlimit: Any | None) -> int:
93
139
  if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
94
140
  CPUlimit = oopsieKwargsie(CPUlimit)
95
141
 
96
- concurrencyLimit: int = int(defineConcurrencyLimit(CPUlimit))
97
142
  from mapFolding.theSSOT import concurrencyPackage
98
143
  if concurrencyPackage == 'numba':
99
144
  from numba import get_num_threads, set_num_threads
145
+ concurrencyLimit: int = defineConcurrencyLimit(CPUlimit, get_num_threads())
100
146
  set_num_threads(concurrencyLimit)
101
147
  concurrencyLimit = get_num_threads()
102
- elif concurrencyPackage == 'algorithm':
103
- concurrencyLimit = 1
148
+ elif concurrencyPackage == 'multiprocessing':
149
+ # When to use multiprocessing.set_start_method https://github.com/hunterhogan/mapFolding/issues/6
150
+ concurrencyLimit = defineConcurrencyLimit(CPUlimit)
104
151
  else:
105
- raise NotImplementedError("This function only supports the 'numba' concurrency package.")
106
-
152
+ raise NotImplementedError(f"I received {concurrencyPackage=} but I don't know what to do with that.")
107
153
  return concurrencyLimit
108
154
 
109
- def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, leavesTotal: int) -> int:
110
- """
111
- Determines whether to divide the computation into tasks and how many divisions.
112
-
113
- Parameters
114
- ----------
115
- computationDivisions (None)
116
- Specifies how to divide computations:
117
- - `None`: no division of the computation into tasks; sets task divisions to 0.
118
- - int: direct set the number of task divisions; cannot exceed the map's total leaves.
119
- - `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
120
- - `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
121
- concurrencyLimit
122
- Maximum number of concurrent tasks allowed.
123
- CPUlimit
124
- for error reporting.
125
- listDimensions
126
- for error reporting.
127
-
128
- Returns
129
- -------
130
- taskDivisions
131
- How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
132
-
133
- Raises
134
- ------
135
- ValueError
136
- If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
137
-
138
- Notes
139
- -----
140
- Task divisions should not exceed total leaves or the folds will be over-counted.
141
- """
142
- taskDivisions = 0
143
- if not computationDivisions:
144
- pass
145
- elif isinstance(computationDivisions, int):
146
- taskDivisions = computationDivisions
147
- 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.
148
- computationDivisions = computationDivisions.lower()
149
- if computationDivisions == 'maximum':
150
- taskDivisions = leavesTotal
151
- elif computationDivisions == 'cpu':
152
- taskDivisions = min(concurrencyLimit, leavesTotal)
153
- else:
154
- raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
155
-
156
- if taskDivisions > leavesTotal:
157
- 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.")
158
- return int(max(0, taskDivisions))
159
-
160
- def outfitCountFolds(mapShape: tuple[int, ...], computationDivisions: int | str | None = None, concurrencyLimit: int = 1) -> ComputationState:
161
- leavesTotal = getLeavesTotal(mapShape)
162
- taskDivisions = getTaskDivisions(computationDivisions, concurrencyLimit, leavesTotal)
163
- computationStateInitialized = ComputationState(mapShape, leavesTotal, taskDivisions)
164
- return computationStateInitialized
155
+ def validateListDimensions(listDimensions: Sequence[int]) -> tuple[int, ...]:
156
+ if not listDimensions:
157
+ raise ValueError("listDimensions is a required parameter.")
158
+ listValidated: list[int] = intInnit(listDimensions, 'listDimensions')
159
+ listNonNegative: list[int] = []
160
+ for dimension in listValidated:
161
+ if dimension < 0:
162
+ raise ValueError(f"Dimension {dimension} must be non-negative")
163
+ listNonNegative.append(dimension)
164
+ dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
165
+ if len(dimensionsValid) < 2:
166
+ 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/.")
167
+ return tuple(sorted(dimensionsValid))
@@ -1,35 +1,8 @@
1
1
  """Filesystem functions for mapFolding package."""
2
- from pathlib import Path
2
+ from pathlib import Path, PurePath
3
+ from typing import Any
3
4
  import os
4
5
 
5
- def saveFoldsTotal(pathFilename: str | os.PathLike[str], foldsTotal: int) -> None:
6
- """
7
- Save foldsTotal with multiple fallback mechanisms.
8
-
9
- Parameters:
10
- pathFilename: Target save location
11
- foldsTotal: Critical computed value to save
12
- """
13
- try:
14
- pathFilenameFoldsTotal = Path(pathFilename)
15
- pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
16
- pathFilenameFoldsTotal.write_text(str(foldsTotal))
17
- except Exception as ERRORmessage:
18
- try:
19
- print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
20
- print(ERRORmessage)
21
- print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
22
- randomnessPlanB = (int(str(foldsTotal).strip()[-1]) + 1) * ['YO_']
23
- filenameInfixUnique = ''.join(randomnessPlanB)
24
- pathFilenamePlanB = os.path.join(os.getcwd(), 'foldsTotal' + filenameInfixUnique + '.txt')
25
- writeStreamFallback = open(pathFilenamePlanB, 'w')
26
- writeStreamFallback.write(str(foldsTotal))
27
- writeStreamFallback.close()
28
- print(str(pathFilenamePlanB))
29
- except Exception:
30
- print(foldsTotal)
31
- return None
32
-
33
6
  def getFilenameFoldsTotal(mapShape: tuple[int, ...]) -> str:
34
7
  """Imagine your computer has been counting folds for 9 days, and when it tries to save your newly discovered value,
35
8
  the filename is invalid. I bet you think this function is more important after that thought experiment.
@@ -85,3 +58,38 @@ def getPathFilenameFoldsTotal(mapShape: tuple[int, ...], pathLikeWriteFoldsTotal
85
58
 
86
59
  pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
87
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
@@ -11,8 +11,8 @@ def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
11
11
 
12
12
  for n, foldingsTotal in sequence.items():
13
13
  mapShape = settings['getMapShape'](n)
14
- mapShape = sorted(mapShape)
15
- dictionaryMapDimensionsToFoldsTotalKnown[tuple(mapShape)] = foldingsTotal
14
+ mapShape = tuple(sorted(mapShape))
15
+ dictionaryMapDimensionsToFoldsTotalKnown[mapShape] = foldingsTotal
16
16
  return dictionaryMapDimensionsToFoldsTotalKnown
17
17
 
18
18
  def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
@@ -144,10 +144,10 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
144
144
  return OEISsequence
145
145
 
146
146
  def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
147
- tryCache = False
147
+ tryCache: bool = False
148
148
  if pathFilenameCache.exists():
149
149
  fileAge: timedelta = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
150
- tryCache: bool = fileAge < timedelta(days=cacheDays)
150
+ tryCache = fileAge < timedelta(days=cacheDays)
151
151
 
152
152
  oeisInformation: str | None = None
153
153
  if tryCache: