mapFolding 0.4.2__py3-none-any.whl → 0.5.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 (32) hide show
  1. mapFolding/__init__.py +3 -2
  2. mapFolding/basecamp.py +12 -14
  3. mapFolding/beDRY.py +81 -58
  4. mapFolding/oeis.py +35 -33
  5. mapFolding/someAssemblyRequired/makeJob.py +8 -7
  6. mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +1 -3
  7. mapFolding/someAssemblyRequired/synthesizeNumba.py +57 -60
  8. mapFolding/someAssemblyRequired/synthesizeNumbaGeneralized.py +102 -30
  9. mapFolding/someAssemblyRequired/synthesizeNumbaJob.py +18 -36
  10. mapFolding/someAssemblyRequired/synthesizeNumbaModules.py +77 -31
  11. mapFolding/syntheticModules/numbaCount.py +158 -0
  12. mapFolding/syntheticModules/numba_doTheNeedful.py +5 -12
  13. mapFolding/theDao.py +105 -105
  14. mapFolding/theSSOT.py +80 -205
  15. mapFolding/theSSOTdatatypes.py +166 -0
  16. {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/METADATA +2 -1
  17. mapFolding-0.5.0.dist-info/RECORD +39 -0
  18. tests/conftest.py +84 -26
  19. tests/test_computations.py +29 -66
  20. tests/test_oeis.py +8 -12
  21. tests/test_other.py +11 -7
  22. tests/test_tasks.py +5 -5
  23. mapFolding/syntheticModules/numba_countInitialize.py +0 -52
  24. mapFolding/syntheticModules/numba_countParallel.py +0 -65
  25. mapFolding/syntheticModules/numba_countSequential.py +0 -67
  26. mapFolding/theSSOTnumba.py +0 -125
  27. mapFolding-0.4.2.dist-info/RECORD +0 -42
  28. tests/test_types.py +0 -5
  29. {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/LICENSE +0 -0
  30. {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/WHEEL +0 -0
  31. {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/entry_points.txt +0 -0
  32. {mapFolding-0.4.2.dist-info → mapFolding-0.5.0.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py CHANGED
@@ -11,6 +11,7 @@ from mapFolding.theSSOT import (
11
11
 
12
12
  # Datatype management
13
13
  from mapFolding.theSSOT import (
14
+ getDatatypeModule,
14
15
  hackSSOTdatatype,
15
16
  hackSSOTdtype,
16
17
  setDatatypeElephino,
@@ -21,7 +22,7 @@ from mapFolding.theSSOT import (
21
22
 
22
23
  # Synthesize modules
23
24
  from mapFolding.theSSOT import (
24
- formatModuleNameDEFAULT,
25
+ formatFilenameModuleDEFAULT,
25
26
  getAlgorithmDispatcher,
26
27
  getAlgorithmSource,
27
28
  getPathJobRootDEFAULT,
@@ -60,7 +61,7 @@ from mapFolding.beDRY import (
60
61
  from mapFolding.basecamp import countFolds
61
62
  from mapFolding.oeis import clearOEIScache, getOEISids, oeisIDfor_n
62
63
 
63
- __all__ = [
64
+ __all__: list[str] = [
64
65
  'clearOEIScache',
65
66
  'countFolds',
66
67
  'getOEISids',
mapFolding/basecamp.py CHANGED
@@ -1,12 +1,13 @@
1
+ from collections.abc import Sequence
1
2
  from mapFolding import computationState, getDispatcherCallable, getPathFilenameFoldsTotal, outfitCountFolds, saveFoldsTotal
2
- from typing import Optional, Sequence, Union
3
- import os
4
-
5
- def countFolds(listDimensions: Sequence[int]
6
- , pathLikeWriteFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None
7
- , computationDivisions: Optional[Union[int, str]] = None
8
- , CPUlimit: Optional[Union[int, float, bool]] = None
9
- , **keywordArguments: Optional[Union[str, bool]]
3
+ from os import PathLike
4
+ from pathlib import Path
5
+
6
+ def countFolds(listDimensions: Sequence[int],
7
+ pathLikeWriteFoldsTotal: str | PathLike[str] | None = None,
8
+ computationDivisions: int | str | None = None,
9
+ CPUlimit: int | float | bool | None = None,
10
+ **keywordArguments: str | bool
10
11
  ) -> int:
11
12
  """Count the total number of possible foldings for a given map dimensions.
12
13
 
@@ -17,7 +18,7 @@ def countFolds(listDimensions: Sequence[int]
17
18
  computationDivisions (None):
18
19
  Whether and how to divide the computational work. See notes for details.
19
20
  CPUlimit (None): This is only relevant if there are `computationDivisions`: whether and how to limit the CPU usage. See notes for details.
20
- **keywordArguments: Datatype management.
21
+ **keywordArguments: Datatype management. See `outfitCountFolds` for details.
21
22
  Returns:
22
23
  foldsTotal: Total number of distinct ways to fold a map of the given dimensions.
23
24
 
@@ -40,16 +41,13 @@ def countFolds(listDimensions: Sequence[int]
40
41
  """
41
42
  stateUniversal: computationState = outfitCountFolds(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit, **keywordArguments)
42
43
 
43
- pathFilenameFoldsTotal = None
44
- if pathLikeWriteFoldsTotal is not None:
45
- pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateUniversal['mapShape'], pathLikeWriteFoldsTotal)
46
-
47
44
  dispatcher = getDispatcherCallable()
48
45
  dispatcher(**stateUniversal)
49
46
 
50
47
  foldsTotal = int(stateUniversal['foldGroups'][0:-1].sum() * stateUniversal['foldGroups'][-1])
51
48
 
52
- if pathFilenameFoldsTotal is not None:
49
+ if pathLikeWriteFoldsTotal is not None:
50
+ pathFilenameFoldsTotal: Path = getPathFilenameFoldsTotal(stateUniversal['mapShape'], pathLikeWriteFoldsTotal)
53
51
  saveFoldsTotal(pathFilenameFoldsTotal, foldsTotal)
54
52
 
55
53
  return foldsTotal
mapFolding/beDRY.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """A relatively stable API for oft-needed functionality."""
2
2
  from mapFolding import (
3
3
  computationState,
4
+ getDatatypeModule,
4
5
  getPathJobRootDEFAULT,
5
6
  hackSSOTdatatype,
6
7
  hackSSOTdtype,
@@ -9,35 +10,39 @@ from mapFolding import (
9
10
  setDatatypeElephino,
10
11
  setDatatypeFoldsTotal,
11
12
  setDatatypeLeavesTotal,
13
+ setDatatypeModule,
12
14
  )
15
+ from collections.abc import Sequence
16
+ from numba import get_num_threads, set_num_threads
13
17
  from numpy import dtype, integer, ndarray
14
18
  from numpy.typing import DTypeLike, NDArray
15
- from typing import Any, List, Optional, Sequence, Tuple, Union
19
+ from pathlib import Path
20
+ from sys import maxsize as sysMaxsize
21
+ from typing import Any
16
22
  from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
17
- import numba
18
23
  import numpy
19
24
  import os
20
- import pathlib
21
- import sys
22
25
 
23
- def getFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int], dtype[integer[Any]]]]) -> str:
24
- """Make a standardized filename for the computed value `foldsTotal`.
26
+ def getFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]]) -> str:
27
+ """Imagine your computer has been counting folds for 70 hours, and when it tries to save your newly discovered value,
28
+ the filename is invalid. I bet you think this function is more important after that thought experiment.
29
+
30
+ Make a standardized filename for the computed value `foldsTotal`.
25
31
 
26
32
  The filename takes into account
27
33
  - the dimensions of the map, aka `mapShape`, aka `listDimensions`
28
34
  - no spaces in the filename
29
- - safe filesystem characters across platforms
35
+ - safe filesystem characters
30
36
  - unique extension
31
- - avoiding potential problems when Python is manipulating the filename, including
32
- - treating the file stem as a valid Python identifier, such as
33
- - not starting with a number
34
- - not using reserved words
35
- - no dashes or other special characters
36
- - uh, I can't remember, but I found some frustrating edge limitations
37
- - if 'p' is still the first character, I picked that because it was the original identifier for the map shape in Lunnan's code
37
+ - Python-safe strings:
38
+ - no starting with a number
39
+ - no reserved words
40
+ - no dashes or other special characters
41
+ - uh, I can't remember, but I found some other frustrating limitations
42
+ - 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
38
43
 
39
44
  Parameters:
40
- mapShape: A sequence of integers representing the dimensions of the map (e.g., [3, 2] for a 3x2 map)
45
+ mapShape: A sequence of integers representing the dimensions of the map.
41
46
 
42
47
  Returns:
43
48
  filenameFoldsTotal: A filename string in format 'pNxM.foldsTotal' where N,M are sorted dimensions
@@ -62,13 +67,13 @@ def getLeavesTotal(listDimensions: Sequence[int]) -> int:
62
67
  else:
63
68
  productDimensions = 1
64
69
  for dimension in listPositive:
65
- if dimension > sys.maxsize // productDimensions:
70
+ if dimension > sysMaxsize // productDimensions:
66
71
  raise OverflowError(f"I received {dimension=} in {listDimensions=}, but the product of the dimensions exceeds the maximum size of an integer on this system.")
67
72
  productDimensions *= dimension
68
73
 
69
74
  return productDimensions
70
75
 
71
- def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int], dtype[integer[Any]]]], pathLikeWriteFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None) -> pathlib.Path:
76
+ def getPathFilenameFoldsTotal(mapShape: Sequence[int] | ndarray[tuple[int], dtype[integer[Any]]], pathLikeWriteFoldsTotal: str | os.PathLike[str] | None = None) -> Path:
72
77
  """Get a standardized path and filename for the computed value `foldsTotal`.
73
78
 
74
79
  If you provide a directory, the function will append a standardized filename. If you provide a filename
@@ -82,7 +87,7 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
82
87
  Returns:
83
88
  pathFilenameFoldsTotal: Absolute path and filename.
84
89
  """
85
- pathLikeSherpa = pathlib.Path(pathLikeWriteFoldsTotal) if pathLikeWriteFoldsTotal is not None else None
90
+ pathLikeSherpa = Path(pathLikeWriteFoldsTotal) if pathLikeWriteFoldsTotal is not None else None
86
91
  if not pathLikeSherpa:
87
92
  pathLikeSherpa = getPathJobRootDEFAULT()
88
93
  if pathLikeSherpa.is_dir():
@@ -95,35 +100,38 @@ def getPathFilenameFoldsTotal(mapShape: Union[Sequence[int], ndarray[Tuple[int],
95
100
  pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
96
101
  return pathFilenameFoldsTotal
97
102
 
98
- def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrencyLimit: int, CPUlimit: Optional[Union[bool, float, int]], listDimensions: Sequence[int]) -> int:
103
+ def getTaskDivisions(computationDivisions: int | str | None, concurrencyLimit: int, CPUlimit: bool | float | int | None, listDimensions: Sequence[int]) -> int:
99
104
  """
100
- Determines whether or how to divide the computation into tasks.
105
+ Determines whether to divide the computation into tasks and how many divisions.
101
106
 
102
107
  Parameters
103
108
  ----------
104
- computationDivisions (None):
109
+ computationDivisions (None)
105
110
  Specifies how to divide computations:
106
- - None: no division of the computation into tasks; sets task divisions to 0
107
- - int: direct set the number of task divisions; cannot exceed the map's total leaves
108
- - "maximum": divides into `leavesTotal`-many `taskDivisions`
109
- - "cpu": limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`
110
- concurrencyLimit:
111
- Maximum number of concurrent tasks allowed
112
- CPUlimit: for error reporting
113
- listDimensions: for error reporting
111
+ - `None`: no division of the computation into tasks; sets task divisions to 0.
112
+ - int: direct set the number of task divisions; cannot exceed the map's total leaves.
113
+ - `'maximum'`: divides into `leavesTotal`-many `taskDivisions`.
114
+ - `'cpu'`: limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`.
115
+ concurrencyLimit
116
+ Maximum number of concurrent tasks allowed.
117
+ CPUlimit
118
+ for error reporting.
119
+ listDimensions
120
+ for error reporting.
114
121
 
115
122
  Returns
116
123
  -------
117
- taskDivisions:
124
+ taskDivisions
125
+ How many tasks must finish before the job can compute the total number of folds; `0` means no tasks, only job.
118
126
 
119
127
  Raises
120
128
  ------
121
- ValueError
122
- If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves
129
+ ValueError
130
+ If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves.
123
131
 
124
132
  Notes
125
133
  -----
126
- Task divisions should not exceed total leaves to prevent duplicate counting of folds.
134
+ Task divisions should not exceed total leaves or the folds will be over-counted.
127
135
  """
128
136
  taskDivisions = 0
129
137
  leavesTotal = getLeavesTotal(listDimensions)
@@ -133,9 +141,9 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
133
141
  taskDivisions = computationDivisions
134
142
  elif isinstance(computationDivisions, str):
135
143
  computationDivisions = computationDivisions.lower()
136
- if computationDivisions == "maximum":
144
+ if computationDivisions == 'maximum':
137
145
  taskDivisions = leavesTotal
138
- elif computationDivisions == "cpu":
146
+ elif computationDivisions == 'cpu':
139
147
  taskDivisions = min(concurrencyLimit, leavesTotal)
140
148
  else:
141
149
  raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
@@ -145,7 +153,7 @@ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrenc
145
153
 
146
154
  return taskDivisions
147
155
 
148
- def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optional[str]) -> ndarray[Tuple[int, int, int], dtype[integer[Any]]]:
156
+ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: str | None) -> ndarray[tuple[int, int, int], dtype[integer[Any]]]:
149
157
  """
150
158
  Constructs a multi-dimensional connection graph representing the connections between the leaves of a map with the given dimensions.
151
159
  Also called a Cartesian product decomposition or dimensional product mapping.
@@ -172,7 +180,7 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
172
180
  for leaf1ndex in range(1, leavesTotal + 1):
173
181
  coordinateSystem[indexDimension, leaf1ndex] = ( ((leaf1ndex - 1) // cumulativeProduct[indexDimension]) % arrayDimensions[indexDimension] + 1 )
174
182
 
175
- connectionGraph = numpy.zeros((dimensionsTotal, leavesTotal + 1, leavesTotal + 1), dtype=dtype)
183
+ connectionGraph: ndarray[tuple[int, int, int], numpy.dtype[integer[Any]]] = numpy.zeros((dimensionsTotal, leavesTotal + 1, leavesTotal + 1), dtype=dtype)
176
184
  for indexDimension in range(dimensionsTotal):
177
185
  for activeLeaf1ndex in range(1, leavesTotal + 1):
178
186
  for connectee1ndex in range(1, activeLeaf1ndex + 1):
@@ -190,23 +198,29 @@ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optio
190
198
 
191
199
  return connectionGraph
192
200
 
193
- def makeDataContainer(shape: Union[int, Tuple[int, ...]], datatype: Optional[DTypeLike] = None) -> NDArray[integer[Any]]:
201
+ def makeDataContainer(shape: int | tuple[int, ...], datatype: DTypeLike | None = None) -> NDArray[integer[Any]]:
194
202
  """Create a zeroed-out `ndarray` with the given shape and datatype.
195
203
 
196
204
  Parameters:
197
205
  shape: The shape of the array. Can be an integer for 1D arrays
198
206
  or a tuple of integers for multi-dimensional arrays.
199
- datatype: The desired data type for the array.
200
- If None, defaults to dtypeLargeDEFAULT. Defaults to None.
207
+ datatype ('dtypeFoldsTotal'): The desired data type for the array.
208
+ If `None`, defaults to 'dtypeFoldsTotal'. Defaults to None.
201
209
 
202
210
  Returns:
203
211
  dataContainer: A new array of given shape and type, filled with zeros.
212
+
213
+ Notes:
214
+ If a version of the algorithm were to use something other than numpy, such as JAX or CUDA, because other
215
+ functions use this function, it would be much easier to change the datatype "ecosystem".
204
216
  """
205
- if datatype is None:
206
- datatype = hackSSOTdtype('dtypeFoldsTotal')
207
- return numpy.zeros(shape, dtype=datatype)
217
+ numpyDtype = datatype or hackSSOTdtype('dtypeFoldsTotal')
218
+ if 'numpy' == getDatatypeModule():
219
+ return numpy.zeros(shape, dtype=numpyDtype)
220
+ else:
221
+ raise NotImplementedError("Somebody done broke it.")
208
222
 
209
- def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Union[str, bool]]) -> computationState:
223
+ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: int | str | None = None, CPUlimit: bool | float | int | None = None, **keywordArguments: str | bool | None) -> computationState:
210
224
  """
211
225
  Initializes and configures the computation state for map folding computations.
212
226
 
@@ -214,11 +228,12 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
214
228
  listDimensions: The dimensions of the map to be folded
215
229
  computationDivisions (None): see `getTaskDivisions`
216
230
  CPUlimit (None): see `setCPUlimit`
217
- **keywordArguments: Datatype management.
231
+ **keywordArguments: Datatype management, it's complicated: see the code below.
218
232
 
219
233
  Returns:
220
234
  stateInitialized: The initialized computation state
221
235
  """
236
+ # keywordArguments START
222
237
  kwourGrapes = keywordArguments.get('sourGrapes', None)
223
238
  if kwourGrapes:
224
239
  sourGrapes = True
@@ -240,6 +255,13 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
240
255
  ImaSetTheDatatype = str(ImaSetTheDatatype)
241
256
  setDatatypeLeavesTotal(ImaSetTheDatatype, sourGrapes)
242
257
 
258
+ # NOTE well: this might be only hypothetical because as of this writing, `makeDataContainer` only makes numpy.zeros. But it's here in case things change.
259
+ ImaSetTheDatatype = keywordArguments.get('datatypeModule', None)
260
+ if ImaSetTheDatatype:
261
+ ImaSetTheDatatype = str(ImaSetTheDatatype)
262
+ setDatatypeModule(ImaSetTheDatatype, sourGrapes)
263
+ # keywordArguments END
264
+
243
265
  my = makeDataContainer(len(indexMy), hackSSOTdtype('my'))
244
266
 
245
267
  mapShape = tuple(sorted(validateListDimensions(listDimensions)))
@@ -263,18 +285,18 @@ def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Option
263
285
 
264
286
  return stateInitialized
265
287
 
266
- def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') -> List[int]:
288
+ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensions') -> list[int]:
267
289
  """
268
- Parse and validate dimensions are non-negative integers.
290
+ Parse and validate the dimensions are non-negative integers.
269
291
 
270
292
  Parameters:
271
- dimensions: Sequence of integers representing dimensions
272
- parameterName ('listDimensions'): Name of the parameter for error messages. Defaults to 'listDimensions'
293
+ dimensions: Sequence of integers representing dimensions.
294
+ parameterName ('listDimensions'): Name of the parameter for error messages. Defaults to 'listDimensions'.
273
295
  Returns:
274
- listNonNegative: List of validated non-negative integers
296
+ listNonNegative: List of validated non-negative integers.
275
297
  Raises:
276
- ValueError: If any dimension is negative or if the list is empty
277
- TypeError: If any element cannot be converted to integer (raised by intInnit)
298
+ ValueError: If any dimension is negative or if the list is empty.
299
+ TypeError: If any element cannot be converted to integer (raised by `intInnit`).
278
300
  """
279
301
  listValidated = intInnit(dimensions, parameterName)
280
302
  listNonNegative = []
@@ -285,7 +307,7 @@ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'listDimensi
285
307
 
286
308
  return listNonNegative
287
309
 
288
- def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int) -> None:
310
+ def saveFoldsTotal(pathFilename: str | os.PathLike[str], foldsTotal: int) -> None:
289
311
  """
290
312
  Save foldsTotal with multiple fallback mechanisms.
291
313
 
@@ -294,7 +316,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
294
316
  foldsTotal: Critical computed value to save
295
317
  """
296
318
  try:
297
- pathFilenameFoldsTotal = pathlib.Path(pathFilename)
319
+ pathFilenameFoldsTotal = Path(pathFilename)
298
320
  pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
299
321
  pathFilenameFoldsTotal.write_text(str(foldsTotal))
300
322
  except Exception as ERRORmessage:
@@ -312,7 +334,7 @@ def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int)
312
334
  except Exception:
313
335
  print(foldsTotal)
314
336
 
315
- def setCPUlimit(CPUlimit: Optional[Any]) -> int:
337
+ def setCPUlimit(CPUlimit: Any | None) -> int:
316
338
  """Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
317
339
 
318
340
  Parameters:
@@ -334,11 +356,12 @@ def setCPUlimit(CPUlimit: Optional[Any]) -> int:
334
356
  CPUlimit = oopsieKwargsie(CPUlimit)
335
357
 
336
358
  concurrencyLimit = int(defineConcurrencyLimit(CPUlimit))
337
- numba.set_num_threads(concurrencyLimit)
359
+ set_num_threads(concurrencyLimit)
360
+ concurrencyLimit = get_num_threads()
338
361
 
339
362
  return concurrencyLimit
340
363
 
341
- def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
364
+ def validateListDimensions(listDimensions: Sequence[int]) -> list[int]:
342
365
  """
343
366
  Validates and sorts a sequence of at least two positive dimensions.
344
367
 
@@ -349,7 +372,7 @@ def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
349
372
  dimensionsValidSorted: A list, with at least two elements, of only positive integers.
350
373
 
351
374
  Raises:
352
- ValueError: If the input listDimensions is None.
375
+ ValueError: If the input listDimensions is empty.
353
376
  NotImplementedError: If the resulting list of positive dimensions has fewer than two elements.
354
377
  """
355
378
  if not listDimensions:
mapFolding/oeis.py CHANGED
@@ -1,8 +1,8 @@
1
- """Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS);
2
- _only_ things that implement _only_ the OEIS."""
1
+ """Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS); _only_ things that implement _only_ the OEIS."""
2
+ from collections.abc import Callable
3
3
  from datetime import datetime, timedelta
4
4
  from mapFolding import countFolds, getPathPackage
5
- from typing import Any, Callable, Dict, Final, List, Tuple, TYPE_CHECKING, Union
5
+ from typing import Any, Final, TYPE_CHECKING
6
6
  import argparse
7
7
  import pathlib
8
8
  import random
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
17
17
  else:
18
18
  TypedDict = dict
19
19
 
20
+ cacheDays = 7
21
+
20
22
  """
21
23
  Section: make `settingsOEIS`"""
22
24
 
@@ -24,15 +26,15 @@ _pathCache = getPathPackage() / ".cache"
24
26
 
25
27
  class SettingsOEIS(TypedDict):
26
28
  description: str
29
+ getMapShape: Callable[[int], list[int]]
27
30
  offset: int
28
- getMapShape: Callable[[int], List[int]]
29
- valuesBenchmark: List[int]
30
- valuesKnown: Dict[int, int]
31
- valuesTestParallelization: List[int]
32
- valuesTestValidation: List[int]
31
+ valuesBenchmark: list[int]
32
+ valuesKnown: dict[int, int]
33
+ valuesTestParallelization: list[int]
34
+ valuesTestValidation: list[int]
33
35
  valueUnknown: int
34
36
 
35
- settingsOEIShardcodedValues: Dict[str, Dict[str, Any]] = {
37
+ settingsOEIShardcodedValues: dict[str, dict[str, Any]] = {
36
38
  'A001415': {
37
39
  'getMapShape': lambda n: sorted([2, n]),
38
40
  'valuesBenchmark': [14],
@@ -65,10 +67,10 @@ settingsOEIShardcodedValues: Dict[str, Dict[str, Any]] = {
65
67
  },
66
68
  }
67
69
 
68
- oeisIDsImplemented: Final[List[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
70
+ oeisIDsImplemented: Final[list[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
69
71
  """Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
70
72
 
71
- def _validateOEISid(oeisIDcandidate: str) -> str:
73
+ def validateOEISid(oeisIDcandidate: str) -> str:
72
74
  """
73
75
  Validates an OEIS sequence ID against implemented sequences.
74
76
 
@@ -98,13 +100,14 @@ def _validateOEISid(oeisIDcandidate: str) -> str:
98
100
  f"Available sequences:\n{_formatOEISsequenceInfo()}"
99
101
  )
100
102
 
101
- def _getFilenameOEISbFile(oeisID: str) -> str:
102
- oeisID = _validateOEISid(oeisID)
103
+ def getFilenameOEISbFile(oeisID: str) -> str:
104
+ oeisID = validateOEISid(oeisID)
103
105
  return f"b{oeisID[1:]}.txt"
104
106
 
105
- def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
107
+ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> dict[int, int]:
106
108
  """
107
109
  Parses the content of an OEIS b-file for a given sequence ID.
110
+
108
111
  This function processes a multiline string representing an OEIS b-file and
109
112
  creates a dictionary mapping integer indices to their corresponding sequence
110
113
  values. The first line of the b-file is expected to contain a comment that
@@ -133,8 +136,7 @@ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
133
136
  OEISsequence[n] = aOFn
134
137
  return OEISsequence
135
138
 
136
- def _getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
137
- cacheDays = 7
139
+ def getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
138
140
  tryCache = False
139
141
  if pathFilenameCache.exists():
140
142
  fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
@@ -144,7 +146,7 @@ def _getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
144
146
  if tryCache:
145
147
  try:
146
148
  oeisInformation = pathFilenameCache.read_text()
147
- except IOError:
149
+ except OSError:
148
150
  tryCache = False
149
151
 
150
152
  if not tryCache:
@@ -158,7 +160,7 @@ def _getOEISofficial(pathFilenameCache: pathlib.Path, url: str) -> None | str:
158
160
 
159
161
  return oeisInformation
160
162
 
161
- def _getOEISidValues(oeisID: str) -> Dict[int, int]:
163
+ def getOEISidValues(oeisID: str) -> dict[int, int]:
162
164
  """
163
165
  Retrieves the specified OEIS sequence as a dictionary mapping integer indices
164
166
  to their corresponding values.
@@ -177,21 +179,21 @@ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
177
179
  IOError: If there is an error reading from or writing to the local cache.
178
180
  """
179
181
 
180
- pathFilenameCache = _pathCache / _getFilenameOEISbFile(oeisID)
181
- url = f"https://oeis.org/{oeisID}/{_getFilenameOEISbFile(oeisID)}"
182
+ pathFilenameCache = _pathCache / getFilenameOEISbFile(oeisID)
183
+ url = f"https://oeis.org/{oeisID}/{getFilenameOEISbFile(oeisID)}"
182
184
 
183
- oeisInformation = _getOEISofficial(pathFilenameCache, url)
185
+ oeisInformation = getOEISofficial(pathFilenameCache, url)
184
186
 
185
187
  if oeisInformation:
186
188
  return _parseBFileOEIS(oeisInformation, oeisID)
187
189
  return {-1: -1}
188
190
 
189
- def _getOEISidInformation(oeisID: str) -> Tuple[str, int]:
190
- oeisID = _validateOEISid(oeisID)
191
+ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
192
+ oeisID = validateOEISid(oeisID)
191
193
  pathFilenameCache = _pathCache / f"{oeisID}.txt"
192
194
  url = f"https://oeis.org/search?q=id:{oeisID}&fmt=text"
193
195
 
194
- oeisInformation = _getOEISofficial(pathFilenameCache, url)
196
+ oeisInformation = getOEISofficial(pathFilenameCache, url)
195
197
 
196
198
  if not oeisInformation:
197
199
  return "Not found", -1
@@ -218,11 +220,11 @@ def _getOEISidInformation(oeisID: str) -> Tuple[str, int]:
218
220
  description = ' '.join(description_parts)
219
221
  return description, offset
220
222
 
221
- def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
223
+ def makeSettingsOEIS() -> dict[str, SettingsOEIS]:
222
224
  settingsTarget = {}
223
225
  for oeisID in oeisIDsImplemented:
224
- valuesKnownSherpa = _getOEISidValues(oeisID)
225
- descriptionSherpa, offsetSherpa = _getOEISidInformation(oeisID)
226
+ valuesKnownSherpa = getOEISidValues(oeisID)
227
+ descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
226
228
  settingsTarget[oeisID] = SettingsOEIS(
227
229
  description=descriptionSherpa,
228
230
  offset=offsetSherpa,
@@ -235,7 +237,7 @@ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
235
237
  )
236
238
  return settingsTarget
237
239
 
238
- settingsOEIS: Dict[str, SettingsOEIS] = makeSettingsOEIS()
240
+ settingsOEIS: dict[str, SettingsOEIS] = makeSettingsOEIS()
239
241
  """All values and settings for `oeisIDsImplemented`."""
240
242
 
241
243
  """
@@ -282,12 +284,12 @@ def oeisIDfor_n(oeisID: str, n: int) -> int:
282
284
  ValueError: If n is negative.
283
285
  KeyError: If the OEIS sequence ID is not directly implemented.
284
286
  """
285
- oeisID = _validateOEISid(oeisID)
287
+ oeisID = validateOEISid(oeisID)
286
288
 
287
289
  if not isinstance(n, int) or n < 0:
288
290
  raise ValueError("`n` must be non-negative integer.")
289
291
 
290
- listDimensions = settingsOEIS[oeisID]['getMapShape'](n)
292
+ listDimensions: list[int] = settingsOEIS[oeisID]['getMapShape'](n)
291
293
 
292
294
  if n <= 1 or len(listDimensions) < 2:
293
295
  offset = settingsOEIS[oeisID]['offset']
@@ -308,9 +310,9 @@ def OEIS_for_n() -> None:
308
310
  parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
309
311
  parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
310
312
 
311
- argumentsCLI = parserCLI.parse_args()
313
+ argumentsCLI: argparse.Namespace = parserCLI.parse_args()
312
314
 
313
- timeStart = time.perf_counter()
315
+ timeStart: float = time.perf_counter()
314
316
 
315
317
  try:
316
318
  print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
@@ -328,7 +330,7 @@ def clearOEIScache() -> None:
328
330
  return
329
331
  for oeisID in settingsOEIS:
330
332
  ( _pathCache / f"{oeisID}.txt" ).unlink(missing_ok=True)
331
- ( _pathCache / _getFilenameOEISbFile(oeisID) ).unlink(missing_ok=True)
333
+ ( _pathCache / getFilenameOEISbFile(oeisID) ).unlink(missing_ok=True)
332
334
  print(f"Cache cleared from {_pathCache}")
333
335
 
334
336
  def getOEISids() -> None:
@@ -1,14 +1,15 @@
1
+ from collections.abc import Sequence
1
2
  from mapFolding import getPathFilenameFoldsTotal, computationState, outfitCountFolds, getAlgorithmSource
3
+ from pathlib import Path
2
4
  from types import ModuleType
3
- from typing import Any, Literal, Optional, Sequence, overload
4
- import pathlib
5
+ from typing import Any, Literal, overload
5
6
  import pickle
6
7
 
7
8
  @overload
8
- def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[True] , **keywordArguments: Optional[str]) -> pathlib.Path: ...
9
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[True] , **keywordArguments: str | None) -> Path: ...
9
10
  @overload
10
- def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[False] , **keywordArguments: Optional[str]) -> computationState: ...
11
- def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keywordArguments: Optional[Any]) -> computationState | pathlib.Path:
11
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[False] , **keywordArguments: str | None) -> computationState: ...
12
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keywordArguments: Any | None) -> computationState | Path:
12
13
  """
13
14
  Creates a computation state job for map folding calculations and optionally saves it to disk.
14
15
 
@@ -27,7 +28,7 @@ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keyw
27
28
 
28
29
  Returns
29
30
  -------
30
- Union[computationState, pathlib.Path]
31
+ Union[computationState, Path]
31
32
  If writeJob is False, returns the computation state object.
32
33
  If writeJob is True, returns the Path object pointing to the saved state file.
33
34
 
@@ -47,7 +48,7 @@ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keyw
47
48
 
48
49
  pathFilenameChopChop = getPathFilenameFoldsTotal(stateUniversal['mapShape'])
49
50
  suffix = pathFilenameChopChop.suffix
50
- pathJob = pathlib.Path(str(pathFilenameChopChop)[0:-len(suffix)])
51
+ pathJob = Path(str(pathFilenameChopChop)[0:-len(suffix)])
51
52
  pathJob.mkdir(parents=True, exist_ok=True)
52
53
  pathFilenameJob = pathJob / 'stateJob.pkl'
53
54
 
@@ -1,15 +1,13 @@
1
1
  from mapFolding import getAlgorithmSource, getPathSyntheticModules
2
2
  from mapFolding import setDatatypeModule, setDatatypeFoldsTotal, setDatatypeElephino, setDatatypeLeavesTotal
3
- from typing import Optional
4
3
  import ast
5
4
  import inspect
6
5
  import pathlib
7
- import sys
8
6
 
9
7
  def transformPythonToJAX(codePython: str) -> None:
10
8
  astPython = ast.parse(codePython)
11
9
 
12
- def writeJax(*, codeSource: Optional[str] = None, pathFilenameAlgorithm: Optional[pathlib.Path] = None, pathFilenameDestination: Optional[pathlib.Path] = None) -> None:
10
+ def writeJax(*, codeSource: str | None = None, pathFilenameAlgorithm: pathlib.Path | None = None, pathFilenameDestination: pathlib.Path | None = None) -> None:
13
11
  if codeSource is None and pathFilenameAlgorithm is None:
14
12
  algorithmSource = getAlgorithmSource()
15
13
  codeSource = inspect.getsource(algorithmSource)