mapFolding 0.7.0__py3-none-any.whl → 0.8.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 (39) hide show
  1. mapFolding/__init__.py +1 -1
  2. mapFolding/basecamp.py +2 -2
  3. mapFolding/beDRY.py +88 -85
  4. mapFolding/filesystem.py +37 -29
  5. mapFolding/noHomeYet.py +2 -2
  6. mapFolding/oeis.py +2 -2
  7. mapFolding/someAssemblyRequired/Z0Z_workbench.py +347 -31
  8. mapFolding/someAssemblyRequired/__init__.py +4 -3
  9. mapFolding/someAssemblyRequired/getLLVMforNoReason.py +0 -1
  10. mapFolding/someAssemblyRequired/ingredientsNumba.py +87 -2
  11. mapFolding/someAssemblyRequired/synthesizeDataConverters.py +34 -52
  12. mapFolding/someAssemblyRequired/{synthesizeNumbaJob.py → synthesizeNumbaJobVESTIGIAL.py} +18 -21
  13. mapFolding/someAssemblyRequired/transformationTools.py +547 -209
  14. mapFolding/syntheticModules/numbaCount_doTheNeedful.py +197 -12
  15. mapFolding/theDao.py +57 -39
  16. mapFolding/theSSOT.py +59 -59
  17. {mapfolding-0.7.0.dist-info → mapfolding-0.8.0.dist-info}/METADATA +6 -7
  18. mapfolding-0.8.0.dist-info/RECORD +41 -0
  19. {mapfolding-0.7.0.dist-info → mapfolding-0.8.0.dist-info}/WHEEL +1 -1
  20. tests/conftest.py +2 -3
  21. tests/test_computations.py +9 -5
  22. tests/test_filesystem.py +0 -2
  23. tests/test_other.py +2 -3
  24. tests/test_tasks.py +7 -5
  25. mapFolding/someAssemblyRequired/synthesizeCountingFunctions.py +0 -7
  26. mapFolding/someAssemblyRequired/synthesizeNumba.py +0 -91
  27. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +0 -91
  28. mapFolding/someAssemblyRequired/whatWillBe.py +0 -311
  29. mapFolding/syntheticModules/__init__.py +0 -0
  30. mapFolding/syntheticModules/dataNamespaceFlattened.py +0 -30
  31. mapFolding/syntheticModules/numbaCount.py +0 -90
  32. mapFolding/syntheticModules/numbaCountSequential.py +0 -110
  33. mapFolding/syntheticModules/numba_doTheNeedful.py +0 -12
  34. mapfolding-0.7.0.dist-info/RECORD +0 -50
  35. /mapFolding/syntheticModules/{numbaCountExample.py → numbaCountHistoricalExample.py} +0 -0
  36. /mapFolding/syntheticModules/{numba_doTheNeedfulExample.py → numba_doTheNeedfulHistoricalExample.py} +0 -0
  37. {mapfolding-0.7.0.dist-info → mapfolding-0.8.0.dist-info}/LICENSE +0 -0
  38. {mapfolding-0.7.0.dist-info → mapfolding-0.8.0.dist-info}/entry_points.txt +0 -0
  39. {mapfolding-0.7.0.dist-info → mapfolding-0.8.0.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py CHANGED
@@ -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__ = [
mapFolding/basecamp.py CHANGED
@@ -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
 
mapFolding/beDRY.py CHANGED
@@ -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))
mapFolding/filesystem.py CHANGED
@@ -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
mapFolding/noHomeYet.py CHANGED
@@ -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:
mapFolding/oeis.py CHANGED
@@ -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: