mapFolding 0.16.4__py3-none-any.whl → 0.17.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 (69) hide show
  1. easyRun/NOTcountingFolds.py +28 -11
  2. easyRun/__init__.py +1 -0
  3. easyRun/countFolds.py +16 -2
  4. easyRun/eliminateFolds.py +60 -0
  5. easyRun/meanders.py +3 -3
  6. mapFolding/__init__.py +2 -1
  7. mapFolding/_theTypes.py +0 -1
  8. mapFolding/algorithms/A086345.py +8 -3
  9. mapFolding/algorithms/__init__.py +1 -1
  10. mapFolding/algorithms/constraintPropagation.py +184 -0
  11. mapFolding/algorithms/elimination.py +131 -0
  12. mapFolding/algorithms/eliminationCount.py +26 -0
  13. mapFolding/algorithms/eliminationPinned.py +35 -0
  14. mapFolding/algorithms/iff.py +206 -0
  15. mapFolding/algorithms/matrixMeanders.py +59 -18
  16. mapFolding/algorithms/matrixMeandersNumPyndas.py +841 -0
  17. mapFolding/algorithms/patternFinder.py +280 -0
  18. mapFolding/algorithms/pinning2Dn.py +345 -0
  19. mapFolding/algorithms/pinning2DnAnnex.py +43 -0
  20. mapFolding/algorithms/symmetricFolds.py +24 -25
  21. mapFolding/basecamp.py +84 -14
  22. mapFolding/beDRY.py +14 -1
  23. mapFolding/dataBaskets.py +86 -71
  24. mapFolding/reference/irvineJavaPort.py +3 -3
  25. mapFolding/reference/meandersDumpingGround/matrixMeandersNumPyV1finalForm.py +1 -1
  26. mapFolding/someAssemblyRequired/A007822/_asynchronousAnnex.py +1 -1
  27. mapFolding/someAssemblyRequired/A007822/makeA007822AsynchronousModules.py +5 -3
  28. mapFolding/someAssemblyRequired/A007822/makeA007822Modules.py +22 -6
  29. mapFolding/someAssemblyRequired/RecipeJob.py +14 -24
  30. mapFolding/someAssemblyRequired/__init__.py +1 -0
  31. mapFolding/someAssemblyRequired/_toolkitContainers.py +6 -4
  32. mapFolding/someAssemblyRequired/infoBooth.py +2 -1
  33. mapFolding/someAssemblyRequired/makeJobTheorem2Numba.py +75 -20
  34. mapFolding/someAssemblyRequired/makeJobTheorem2codon.py +9 -10
  35. mapFolding/someAssemblyRequired/makingModules_count.py +20 -22
  36. mapFolding/someAssemblyRequired/makingModules_doTheNeedful.py +9 -9
  37. mapFolding/someAssemblyRequired/mapFoldingModules/makeMapFoldingModules.py +6 -5
  38. mapFolding/someAssemblyRequired/meanders/makeMeandersModules.py +6 -6
  39. mapFolding/someAssemblyRequired/toolkitMakeModules.py +3 -29
  40. mapFolding/someAssemblyRequired/toolkitNumba.py +2 -1
  41. mapFolding/someAssemblyRequired/transformationTools.py +2 -3
  42. mapFolding/syntheticModules/A007822/algorithm.py +8 -8
  43. mapFolding/syntheticModules/A007822/asynchronous.py +12 -13
  44. mapFolding/syntheticModules/A007822/initializeState.py +10 -8
  45. mapFolding/syntheticModules/A007822/theorem2.py +10 -8
  46. mapFolding/syntheticModules/A007822/theorem2Numba.py +20 -16
  47. mapFolding/syntheticModules/A007822/theorem2Trimmed.py +10 -8
  48. mapFolding/syntheticModules/countParallelNumba.py +5 -2
  49. mapFolding/syntheticModules/daoOfMapFoldingNumba.py +4 -2
  50. mapFolding/syntheticModules/initializeState.py +1 -1
  51. mapFolding/syntheticModules/meanders/bigInt.py +52 -15
  52. mapFolding/syntheticModules/theorem2.py +1 -1
  53. mapFolding/syntheticModules/theorem2Numba.py +4 -2
  54. mapFolding/syntheticModules/theorem2Trimmed.py +1 -1
  55. mapFolding/tests/conftest.py +1 -1
  56. mapFolding/tests/test_computations.py +21 -4
  57. mapFolding/tests/verify.py +323 -0
  58. {mapfolding-0.16.4.dist-info → mapfolding-0.17.1.dist-info}/METADATA +14 -11
  59. mapfolding-0.17.1.dist-info/RECORD +112 -0
  60. easyRun/A000682.py +0 -25
  61. easyRun/A005316.py +0 -20
  62. mapFolding/algorithms/matrixMeandersBeDry.py +0 -182
  63. mapFolding/algorithms/matrixMeandersNumPy.py +0 -333
  64. mapFolding/algorithms/matrixMeandersPandas.py +0 -334
  65. mapfolding-0.16.4.dist-info/RECORD +0 -106
  66. {mapfolding-0.16.4.dist-info → mapfolding-0.17.1.dist-info}/WHEEL +0 -0
  67. {mapfolding-0.16.4.dist-info → mapfolding-0.17.1.dist-info}/entry_points.txt +0 -0
  68. {mapfolding-0.16.4.dist-info → mapfolding-0.17.1.dist-info}/licenses/LICENSE +0 -0
  69. {mapfolding-0.16.4.dist-info → mapfolding-0.17.1.dist-info}/top_level.txt +0 -0
@@ -3,34 +3,33 @@
3
3
  Notes
4
4
  -----
5
5
  - About constructing `leafComparison`:
6
- - This branch of the algorithm executes IFF `leafBelow[0] == 1`.
7
- - Therefore, `leafComparison[0]` must be `1`.
8
- - Therefore, the first iteration of the loop is hardcoded to save processing time.
9
- - I _feel_ there must be a more efficient way to do this.
6
+ - The first iteration of the loop is hardcoded to save processing time.
7
+ - I _feel_ there must be a more efficient way to do this.
10
8
  - Some implementation details are based on Numba compatibility. Incompatible:
11
- - `numpy.take(..., out=...)`
12
- - `numpy.all(..., axis=...)`
9
+ - `numpy.take(..., out=...)`
10
+ - `numpy.all(..., axis=...)`
13
11
  """
14
12
  from mapFolding.dataBaskets import SymmetricFoldsState
15
- import numpy
16
13
 
17
14
  def filterAsymmetricFolds(state: SymmetricFoldsState) -> SymmetricFoldsState:
18
- state.indexLeaf = 1
19
- state.leafComparison[0] = 1
20
- state.leafConnectee = 1
21
-
22
- while state.leafConnectee < state.leavesTotal + 1:
23
- state.indexMiniGap = state.leafBelow[state.indexLeaf]
24
- state.leafComparison[state.leafConnectee] = (state.indexMiniGap - state.indexLeaf + state.leavesTotal) % state.leavesTotal
25
- state.indexLeaf = state.indexMiniGap
26
-
27
- state.leafConnectee += 1
28
-
29
- state.arrayGroupOfFolds = numpy.take(state.leafComparison, state.indicesArrayGroupOfFolds)
30
- compared = state.arrayGroupOfFolds[..., 0:state.leavesTotal // 2] == state.arrayGroupOfFolds[..., state.leavesTotal // 2:None]
31
-
32
- for indexRow in range(len(compared)):
33
- state.groupsOfFolds += compared[indexRow].all()
34
-
35
- return state
15
+ state.indexLeaf = 1
16
+ state.leafComparison[0] = 1
17
+ state.leafConnectee = 1
18
+
19
+ while state.leafConnectee < state.leavesTotal + 1:
20
+ state.indexMiniGap = state.leafBelow[state.indexLeaf]
21
+ state.leafComparison[state.leafConnectee] = (state.indexMiniGap - state.indexLeaf + state.leavesTotal) % state.leavesTotal
22
+ state.indexLeaf = state.indexMiniGap
23
+
24
+ state.leafConnectee += 1
25
+
26
+ for listTuples in state.indices:
27
+ state.leafConnectee = 1
28
+ for indexLeft, indexRight in listTuples:
29
+ if state.leafComparison[indexLeft] != state.leafComparison[indexRight]:
30
+ state.leafConnectee = 0
31
+ break
32
+ state.symmetricFolds += state.leafConnectee
33
+
34
+ return state
36
35
 
mapFolding/basecamp.py CHANGED
@@ -169,18 +169,20 @@ def countFolds(listDimensions: Sequence[int] | None = None
169
169
 
170
170
  mapFoldingParallelState: ParallelMapFoldingState = ParallelMapFoldingState(mapShape, taskDivisions=taskDivisions)
171
171
 
172
- # `listStatesParallel` exists so you can research the parallel computation.
172
+ # NOTE `listStatesParallel` exists so you can research the parallel computation.
173
173
  foldsTotal, _listStatesParallel = doTheNeedful(mapFoldingParallelState, concurrencyLimit)
174
174
 
175
175
  # ruff: noqa: E701
176
176
  else:
177
- match flow:
178
- case 'daoOfMapFolding': from mapFolding.algorithms.daoOfMapFolding import doTheNeedful
179
- case 'numba': from mapFolding.syntheticModules.daoOfMapFoldingNumba import doTheNeedful
180
- case 'theorem2': from mapFolding.syntheticModules.theorem2 import doTheNeedful
181
- case 'theorem2Numba': from mapFolding.syntheticModules.theorem2Numba import doTheNeedful
182
- case 'theorem2Trimmed': from mapFolding.syntheticModules.theorem2Trimmed import doTheNeedful
183
- case _: from mapFolding.algorithms.daoOfMapFolding import doTheNeedful
177
+ if all(dimension < 2 for dimension in mapShape):
178
+ from mapFolding.algorithms.daoOfMapFolding import doTheNeedful
179
+ else:
180
+ match flow:
181
+ case 'numba': from mapFolding.syntheticModules.daoOfMapFoldingNumba import doTheNeedful
182
+ case 'theorem2': from mapFolding.syntheticModules.theorem2 import doTheNeedful
183
+ case 'theorem2Numba': from mapFolding.syntheticModules.theorem2Numba import doTheNeedful
184
+ case 'theorem2Trimmed': from mapFolding.syntheticModules.theorem2Trimmed import doTheNeedful
185
+ case 'daoOfMapFolding' | _: from mapFolding.algorithms.daoOfMapFolding import doTheNeedful
184
186
 
185
187
  from mapFolding.dataBaskets import MapFoldingState
186
188
  mapFoldingState: MapFoldingState = MapFoldingState(mapShape)
@@ -194,6 +196,75 @@ def countFolds(listDimensions: Sequence[int] | None = None
194
196
 
195
197
  return foldsTotal
196
198
 
199
+ def eliminateFolds(mapShape: tuple[int, ...]
200
+ , pathLikeWriteFoldsTotal: PathLike[str] | PurePath | None = None
201
+ # , * # TODO improve `standardizedEqualToCallableReturn` so it will work with keyword arguments
202
+ , CPUlimit: bool | float | int | None = None # noqa: FBT001
203
+ , flow: str | None = None
204
+ ) -> int:
205
+ """
206
+ Compute foldsTotal by elimination.
207
+
208
+ Parameters
209
+ ----------
210
+ mapShape : tuple[int, ...] | None = None
211
+ Tuple of integers representing the dimensions of the map to be folded. Mathematicians almost always use the term
212
+ "dimensions", such as in the seminal paper, "Multi-dimensional map-folding". Nevertheless, in contemporary Python
213
+ programming, in the context of these algorithms, the term "shape" makes it much easier to align the mathematics with the
214
+ syntax of the programming language.
215
+ pathLikeWriteFoldsTotal : PathLike[str] | PurePath | None = None
216
+ A filename, a path of only directories, or a path with directories and a filename to which `countFolds` will write the
217
+ value of `foldsTotal`. If `pathLikeWriteFoldsTotal` is a path of only directories, `countFolds` creates a filename based
218
+ on the map dimensions.
219
+ CPUlimit : bool | float | int | None = None
220
+ If relevant, whether and how to limit the number of processors `countFolds` will use.
221
+ - `False`, `None`, or `0`: No limits on processor usage; uses all available processors. All other values will
222
+ potentially limit processor usage.
223
+ - `True`: Yes, limit the processor usage; limits to 1 processor.
224
+ - `int >= 1`: The maximum number of available processors to use.
225
+ - `0 < float < 1`: The maximum number of processors to use expressed as a fraction of available processors.
226
+ - `-1 < float < 0`: The number of processors to *not* use expressed as a fraction of available processors.
227
+ - `int <= -1`: The number of available processors to *not* use.
228
+ - If the value of `CPUlimit` is a `float` greater than 1 or less than -1, `countFolds` truncates the value to an `int`
229
+ with the same sign as the `float`.
230
+ flow : str | None = None
231
+ My stupid way of selecting the version of the algorithm to use in the computation.
232
+
233
+ Returns
234
+ -------
235
+ foldsTotal : int
236
+ Number of distinct ways to fold a map of the given dimensions.
237
+ """
238
+ from mapFolding.beDRY import setProcessorLimit
239
+ concurrencyLimit: int = setProcessorLimit(CPUlimit, packageSettings.concurrencyPackage)
240
+
241
+ # ------- memorialization instructions ---------------------------------------------
242
+
243
+ if pathLikeWriteFoldsTotal is not None:
244
+ pathFilenameFoldsTotal: Path | None = getPathFilenameFoldsTotal(mapShape, pathLikeWriteFoldsTotal)
245
+ saveFoldsTotalFAILearly(pathFilenameFoldsTotal)
246
+ else:
247
+ pathFilenameFoldsTotal = None
248
+
249
+ # ------- Algorithm version -----------------------------------------------------
250
+ # ruff: noqa: E701
251
+ match flow:
252
+ case 'constraintPropagation': from mapFolding.algorithms.constraintPropagation import doTheNeedful
253
+ case 'pinned': from mapFolding.algorithms.eliminationPinned import doTheNeedful
254
+ case 'elimination' | _: from mapFolding.algorithms.elimination import doTheNeedful
255
+
256
+ from mapFolding.dataBaskets import EliminationState
257
+ eliminationState: EliminationState = EliminationState(mapShape)
258
+ eliminationState = doTheNeedful(eliminationState, concurrencyLimit)
259
+ foldsTotal = eliminationState.foldsTotal
260
+
261
+ # ------- Follow memorialization instructions ---------------------------------------------
262
+
263
+ if pathFilenameFoldsTotal is not None:
264
+ saveFoldsTotal(pathFilenameFoldsTotal, foldsTotal)
265
+
266
+ return foldsTotal
267
+
197
268
  def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None
198
269
  # TODO , pathLikeWriteFoldsTotal: PathLike[str] | PurePath | None = None
199
270
  , CPUlimit: bool | float | int | None = None # noqa: FBT001
@@ -225,12 +296,11 @@ def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None
225
296
  case 'A000682' | 'A005316':
226
297
  match flow:
227
298
  case 'matrixNumPy':
228
- from mapFolding.algorithms.matrixMeandersNumPy import doTheNeedful
229
- from mapFolding.dataBaskets import MatrixMeandersNumPyState as State
299
+ from mapFolding.algorithms.matrixMeandersNumPyndas import doTheNeedful, MatrixMeandersNumPyState as State
230
300
  case 'matrixPandas':
231
- from mapFolding.algorithms.matrixMeandersPandas import doTheNeedful
232
- from mapFolding.dataBaskets import MatrixMeandersNumPyState as State
233
- case _:
301
+ from mapFolding.algorithms.matrixMeandersNumPyndas import (
302
+ doTheNeedfulPandas as doTheNeedful, MatrixMeandersNumPyState as State)
303
+ case 'matrixMeanders' | _:
234
304
  from mapFolding.algorithms.matrixMeanders import doTheNeedful
235
305
  from mapFolding.dataBaskets import MatrixMeandersState as State
236
306
 
@@ -291,7 +361,7 @@ def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None
291
361
  from mapFolding.syntheticModules.A007822.algorithm import doTheNeedful
292
362
  symmetricState = doTheNeedful(symmetricState)
293
363
 
294
- countTotal = symmetricState.groupsOfFolds
364
+ countTotal = symmetricState.symmetricFolds
295
365
  case _:
296
366
  matched_oeisID = False
297
367
 
mapFolding/beDRY.py CHANGED
@@ -1,13 +1,26 @@
1
1
  """Oft-needed computations or actions, especially for multi-dimensional map folding."""
2
2
 
3
- from collections.abc import Sequence
3
+ from collections.abc import Iterable, Iterator, Sequence
4
+ from functools import cache
4
5
  from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
5
6
  from mapFolding import NumPyIntegerType
7
+ from more_itertools import extract
6
8
  from numpy import dtype as numpy_dtype, int64 as numpy_int64, ndarray
7
9
  from sys import maxsize as sysMaxsize
8
10
  from typing import Any
9
11
  import numpy
10
12
 
13
+ def exclude[个](iterable: Sequence[个], indices: Iterable[int]) -> Iterator[个]:
14
+ """Yield items from `iterable` whose positions are not in `indices`."""
15
+ lengthIterable: int = len(iterable)
16
+ def normalizeIndex(index: int) -> int:
17
+ if index < 0:
18
+ index = (index + lengthIterable) % lengthIterable
19
+ return index
20
+ indicesInclude: list[int] = sorted(set(range(lengthIterable)).difference(map(normalizeIndex, indices)))
21
+ return extract(iterable, indicesInclude)
22
+
23
+ @cache
11
24
  def getLeavesTotal(mapShape: tuple[int, ...]) -> int:
12
25
  """Calculate the total number of leaves in a map with the given dimensions.
13
26
 
mapFolding/dataBaskets.py CHANGED
@@ -21,12 +21,65 @@ integrity throughout the recursive analysis while providing the structured data
21
21
  access patterns that enable efficient result persistence and retrieval.
22
22
  """
23
23
  from mapFolding import (
24
- Array1DElephino, Array1DLeavesTotal, Array2DLeavesTotal, Array3DLeavesTotal, DatatypeElephino, DatatypeFoldsTotal,
25
- DatatypeLeavesTotal, getConnectionGraph, getLeavesTotal, makeDataContainer)
26
- from numpy.typing import NDArray
27
- from typing import TypeAlias
24
+ Array1DElephino, Array1DLeavesTotal, Array3DLeavesTotal, DatatypeElephino, DatatypeFoldsTotal, DatatypeLeavesTotal,
25
+ getConnectionGraph, getLeavesTotal, makeDataContainer)
28
26
  import dataclasses
29
- import numpy
27
+
28
+ @dataclasses.dataclass(slots=True)
29
+ class EliminationState:
30
+ """Computational state for algorithms to compute foldsTotal by elimination.
31
+
32
+ Attributes
33
+ ----------
34
+ mapShape : tuple[DatatypeLeavesTotal, ...]
35
+ Dimensions of the map being analyzed for folding patterns.
36
+ groupsOfFolds : DatatypeFoldsTotal = DatatypeFoldsTotal(0)
37
+ Current count of distinct folding pattern groups: each group has `leavesTotal`-many foldings.
38
+ dimensionsTotal : DatatypeLeavesTotal
39
+ Unchanging total number of dimensions in the map.
40
+ leavesTotal : DatatypeLeavesTotal
41
+ Unchanging total number of leaves in the map.
42
+ """
43
+
44
+ mapShape: tuple[DatatypeLeavesTotal, ...] = dataclasses.field(init=True, metadata={'elementConstructor': 'DatatypeLeavesTotal'})
45
+ """Dimensions of the map being analyzed for folding patterns."""
46
+
47
+ groupsOfFolds: DatatypeFoldsTotal = dataclasses.field(default=DatatypeFoldsTotal(0), metadata={'theCountingIdentifier': True})
48
+ """Current count of distinct folding pattern groups: each group has `leavesTotal`-many foldings."""
49
+
50
+ listPinnedLeaves: list[dict[int, int]] = dataclasses.field(default_factory=list[dict[int, int]], init=True)
51
+ """column: leaf or pile: indexLeaf"""
52
+ pile: DatatypeLeavesTotal = DatatypeLeavesTotal(-1) # noqa: RUF009
53
+ pinnedLeaves: dict[int, int] = dataclasses.field(default_factory=dict[int, int], init=True, metadata={'elementConstructor': 'DatatypeLeavesTotal'})
54
+ """column: leaf or pile: indexLeaf"""
55
+
56
+ subsetsTheorem2: DatatypeLeavesTotal = DatatypeLeavesTotal(1) # noqa: RUF009
57
+ subsetsTheorem3: DatatypeLeavesTotal = DatatypeLeavesTotal(1) # noqa: RUF009
58
+ subsetsTheorem4: DatatypeLeavesTotal = DatatypeLeavesTotal(1) # noqa: RUF009
59
+
60
+ columnLast: DatatypeLeavesTotal = dataclasses.field(init=False)
61
+ dimensionsTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
62
+ """Unchanging total number of dimensions in the map."""
63
+ leavesTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
64
+ """Unchanging total number of leaves in the map."""
65
+
66
+ @property
67
+ def foldsTotal(self) -> DatatypeFoldsTotal:
68
+ """The total number of possible folding patterns for this map.
69
+
70
+ Returns
71
+ -------
72
+ foldsTotal : DatatypeFoldsTotal
73
+ The complete count of distinct folding patterns achievable with the current map configuration.
74
+
75
+ """
76
+ return DatatypeFoldsTotal(self.leavesTotal) * self.groupsOfFolds * self.subsetsTheorem2 * self.subsetsTheorem3 * self.subsetsTheorem4
77
+
78
+ def __post_init__(self) -> None:
79
+ """Ensure all fields have a value."""
80
+ self.dimensionsTotal = DatatypeLeavesTotal(len(self.mapShape))
81
+ self.leavesTotal = DatatypeLeavesTotal(getLeavesTotal(self.mapShape))
82
+ self.columnLast = self.leavesTotal - DatatypeLeavesTotal(1)
30
83
 
31
84
  @dataclasses.dataclass(slots=True)
32
85
  class MapFoldingState:
@@ -203,7 +256,7 @@ class SymmetricFoldsState:
203
256
  mapShape: tuple[DatatypeLeavesTotal, ...] = dataclasses.field(init=True, metadata={'elementConstructor': 'DatatypeLeavesTotal'})
204
257
  """Dimensions of the map being analyzed for folding patterns."""
205
258
 
206
- groupsOfFolds: DatatypeFoldsTotal = dataclasses.field(default=DatatypeFoldsTotal(0), metadata={'theCountingIdentifier': True})
259
+ symmetricFolds: DatatypeFoldsTotal = dataclasses.field(default=DatatypeFoldsTotal(0), metadata={'theCountingIdentifier': True})
207
260
  """Current count of symmetric folds."""
208
261
 
209
262
  gap1ndex: DatatypeElephino = DatatypeElephino(0) # noqa: RUF009
@@ -237,12 +290,12 @@ class SymmetricFoldsState:
237
290
  leafComparison: Array1DLeavesTotal = dataclasses.field(default=None, init=True, metadata={'dtype': Array1DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportAssignmentType, reportAttributeAccessIssue, reportUnknownMemberType]
238
291
  """Array for finding symmetric folds."""
239
292
 
240
- arrayGroupOfFolds: Array2DLeavesTotal = dataclasses.field(init=False, metadata={'dtype': Array2DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
241
293
  connectionGraph: Array3DLeavesTotal = dataclasses.field(init=False, metadata={'dtype': Array3DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
242
294
  """Unchanging array representing connections between all leaves."""
243
295
  dimensionsTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
244
296
  """Unchanging total number of dimensions in the map."""
245
- indicesArrayGroupOfFolds: Array2DLeavesTotal = dataclasses.field(init=False, metadata={'dtype': Array2DLeavesTotal.__args__[1].__args__[0]}) # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue]
297
+ indices: list[list[tuple[int, int]]] = dataclasses.field(init=False)
298
+ """Precomputed index pairs for symmetric fold checking."""
246
299
  leavesTotal: DatatypeLeavesTotal = dataclasses.field(init=False)
247
300
  """Unchanging total number of leaves in the map."""
248
301
 
@@ -259,12 +312,9 @@ class SymmetricFoldsState:
259
312
  self.leavesTotal = DatatypeLeavesTotal(getLeavesTotal(self.mapShape))
260
313
 
261
314
  leavesTotalAsInt = int(self.leavesTotal)
262
-
263
315
  self.connectionGraph = getConnectionGraph(self.mapShape, leavesTotalAsInt, self.__dataclass_fields__['connectionGraph'].metadata['dtype'])
264
316
 
265
- self.indicesArrayGroupOfFolds = (numpy.arange(leavesTotalAsInt + 1)[:, None] + numpy.arange(leavesTotalAsInt)[None, :]) % (leavesTotalAsInt + 1)
266
- self.indicesArrayGroupOfFolds[..., leavesTotalAsInt // 2:None] = self.indicesArrayGroupOfFolds[..., leavesTotalAsInt - 1: leavesTotalAsInt - leavesTotalAsInt // 2 - 1: -1]
267
- self.arrayGroupOfFolds = numpy.zeros_like(self.indicesArrayGroupOfFolds)
317
+ self.indices = [[((index + folding) % (self.leavesTotal+1), (-2-index + folding) % (self.leavesTotal+1)) for index in range(self.leavesTotal//2)] for folding in range(self.leavesTotal + 1)]
268
318
 
269
319
  if self.dimensionsUnconstrained is None: self.dimensionsUnconstrained = DatatypeLeavesTotal(int(self.dimensionsTotal)) # pyright: ignore[reportUnnecessaryComparison] # noqa: E701
270
320
  if self.gapsWhere is None: self.gapsWhere = makeDataContainer(leavesTotalAsInt * leavesTotalAsInt + 1, self.__dataclass_fields__['gapsWhere'].metadata['dtype']) # pyright: ignore[reportUnnecessaryComparison] # noqa: E701
@@ -398,6 +448,7 @@ class MatrixMeandersState:
398
448
  """The index of the meanders problem being solved."""
399
449
  oeisID: str
400
450
  """'A000682', semi-meanders, or 'A005316', meanders."""
451
+
401
452
  boundary: int
402
453
  """The algorithm analyzes `n` boundaries starting at `boundary = n - 1`."""
403
454
  dictionaryMeanders: dict[int, int]
@@ -408,14 +459,19 @@ class MatrixMeandersState:
408
459
  bitWidth: int = 0
409
460
  """At the start of an iteration enumerated by `boundary`, the number of bits of the largest value `arcCode`. The
410
461
  `dataclass` computes a `property` from `bitWidth`."""
411
-
412
- @property
413
- def MAXIMUMarcCode(self) -> int:
414
- """Compute the maximum value of `arcCode` for the current iteration of the transfer matrix."""
415
- return 1 << (2 * self.boundary + 4)
416
-
417
- @property
418
- def locatorBits(self) -> int:
462
+ bitsLocator: int = 0
463
+ """An odd-parity bit-mask with `bitWidth` bits."""
464
+ MAXIMUMarcCode: int = 0
465
+ """The maximum value of `arcCode` for the current iteration of the transfer matrix."""
466
+
467
+ def reduceBoundary(self) -> None:
468
+ """Prepare for the next iteration of the transfer matrix algorithm by reducing `boundary` by 1 and updating related fields."""
469
+ self.boundary -= 1
470
+ self.setBitWidth()
471
+ self.setBitsLocator()
472
+ self.setMAXIMUMarcCode()
473
+
474
+ def setBitsLocator(self) -> None:
419
475
  """Compute an odd-parity bit-mask with `bitWidth` bits.
420
476
 
421
477
  Notes
@@ -429,60 +485,19 @@ class MatrixMeandersState:
429
485
  odd numbers from even numbers, so I avoid using "odd" and "even" in the names of these bit-masks.
430
486
 
431
487
  """
432
- return sum(1 << one for one in range(0, self.bitWidth, 2))
433
-
434
- @dataclasses.dataclass(slots=True)
435
- class MatrixMeandersNumPyState(MatrixMeandersState):
436
- """Hold the state of a meanders transfer matrix algorithm computation."""
437
-
438
- arrayArcCodes: NDArray[numpy.uint64] = dataclasses.field(default_factory=lambda: numpy.empty((0,), dtype=numpy.uint64))
439
- arrayCrossings: NDArray[numpy.uint64] = dataclasses.field(default_factory=lambda: numpy.empty((0,), dtype=numpy.uint64))
488
+ self.bitsLocator = sum(1 << one for one in range(0, self.bitWidth, 2))
440
489
 
441
- bitWidthLimitArcCode: int | None = None
442
- bitWidthLimitCrossings: int | None = None
490
+ def setBitWidth(self) -> None:
491
+ """Set `bitWidth` from the current `dictionaryMeanders`."""
492
+ self.bitWidth = max(self.dictionaryMeanders.keys()).bit_length()
443
493
 
444
- datatypeArcCode: TypeAlias = numpy.uint64 # noqa: UP040
445
- """The fixed-size integer type used to store `arcCode`."""
446
- datatypeCrossings: TypeAlias = numpy.uint64 # noqa: UP040
447
- """The fixed-size integer type used to store `crossings`."""
448
-
449
- indexTarget: int = 0
450
- """What is being indexed depends on the algorithm flavor."""
494
+ def setMAXIMUMarcCode(self) -> None:
495
+ """Compute the maximum value of `arcCode` for the current iteration of the transfer matrix."""
496
+ self.MAXIMUMarcCode = 1 << (2 * self.boundary + 4)
451
497
 
452
498
  def __post_init__(self) -> None:
453
499
  """Post init."""
454
- if self.bitWidthLimitArcCode is None:
455
- _bitWidthOfFixedSizeInteger: int = numpy.dtype(self.datatypeArcCode).itemsize * 8 # bits
456
-
457
- _offsetNecessary: int = 3 # For example, `bitsZulu << 3`.
458
- _offsetSafety: int = 1 # I don't have mathematical proof of how many extra bits I need.
459
- _offset: int = _offsetNecessary + _offsetSafety
460
-
461
- self.bitWidthLimitArcCode = _bitWidthOfFixedSizeInteger - _offset
462
-
463
- del _bitWidthOfFixedSizeInteger, _offsetNecessary, _offsetSafety, _offset
464
-
465
- if self.bitWidthLimitCrossings is None:
466
- _bitWidthOfFixedSizeInteger: int = numpy.dtype(self.datatypeCrossings).itemsize * 8 # bits
467
-
468
- _offsetNecessary: int = 0 # I don't know of any.
469
- _offsetEstimation: int = 3 # See reference directory.
470
- _offsetSafety: int = 1
471
- _offset: int = _offsetNecessary + _offsetEstimation + _offsetSafety
472
-
473
- self.bitWidthLimitCrossings = _bitWidthOfFixedSizeInteger - _offset
474
-
475
- del _bitWidthOfFixedSizeInteger, _offsetNecessary, _offsetEstimation, _offsetSafety, _offset
476
-
477
- def makeDictionary(self) -> None:
478
- """Convert from NumPy `ndarray` (*Num*erical *Py*thon *n-d*imensional array) to Python `dict` (*dict*ionary)."""
479
- self.dictionaryMeanders = {int(key): int(value) for key, value in zip(self.arrayArcCodes, self.arrayCrossings, strict=True)}
480
- self.arrayArcCodes = numpy.empty((0,), dtype=self.datatypeArcCode)
481
- self.arrayCrossings = numpy.empty((0,), dtype=self.datatypeCrossings)
500
+ self.setBitWidth()
501
+ self.setBitsLocator()
502
+ self.setMAXIMUMarcCode()
482
503
 
483
- def makeArray(self) -> None:
484
- """Convert from Python `dict` (*dict*ionary) to NumPy `ndarray` (*Num*erical *Py*thon *n-d*imensional array)."""
485
- self.arrayArcCodes = numpy.array(list(self.dictionaryMeanders.keys()), dtype=self.datatypeArcCode)
486
- self.arrayCrossings = numpy.array(list(self.dictionaryMeanders.values()), dtype=self.datatypeCrossings)
487
- self.bitWidth = int(self.arrayArcCodes.max()).bit_length()
488
- self.dictionaryMeanders = {}
@@ -1,5 +1,5 @@
1
- """
2
- Ported from the Java version by Sean A. Irvine:
1
+ """Ported from the Java version by Sean A. Irvine.
2
+
3
3
  https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java
4
4
 
5
5
  This implementation is a Python version of a Java implementation of Lunnon's algorithm by Sean A. Irvine.
@@ -9,7 +9,7 @@ Key characteristics:
9
9
  - A procedural paradigm more similar to Lunnon and unlike Irvine's object-oriented implementation.
10
10
  - Only primitive Python data structures.
11
11
 
12
- Citation: https://github.com/hunterhogan/mapFolding/blob/134f2e6ecdf59fb6f6829c775475544a6aaaa800/citations/jOEIS.bibtex
12
+ Citation: https://github.com/hunterhogan/mapFolding/blob/134f2e6ecdf59fb6f6829c775475544a6aaaa800/citations/jOEIS.bib
13
13
  """
14
14
 
15
15
  def foldings(p: list[int], res: int = 0, mod: int = 0) -> int:
@@ -360,7 +360,7 @@ def doTheNeedful(n: int, dictionaryCurveLocations: dict[int, int]) -> int:
360
360
  --------------
361
361
 
362
362
  As first computed by Iwan Jensen in 2000, A000682(41) = 6664356253639465480.
363
- Citation: https://github.com/hunterhogan/mapFolding/blob/main/citations/Jensen.bibtex
363
+ Citation: https://github.com/hunterhogan/mapFolding/blob/main/citations/Jensen.bib
364
364
  See also https://oeis.org/A000682
365
365
 
366
366
  I'm sure you instantly observed that A000682(41) = (6664356253639465480).bit_length() = 63 bits. And A005316(44) =
@@ -34,7 +34,7 @@ def _threadDoesSomething() -> None:
34
34
  break
35
35
  state = _filterAsymmetricFolds(state)
36
36
  with LOCKsymmetricFoldsTotal:
37
- symmetricFoldsTotal += state.groupsOfFolds
37
+ symmetricFoldsTotal += state.symmetricFolds
38
38
 
39
39
  def _filterAsymmetricFolds(state: SymmetricFoldsState) -> SymmetricFoldsState:
40
40
  """Add real function during generation; the signature is here to preview its interactions with the module."""
@@ -1,10 +1,12 @@
1
1
  """addSymmetryCheckAsynchronous."""
2
- from astToolkit import Be, Grab, identifierDotAttribute, LedgerOfImports, Make, NodeChanger, NodeTourist, Then
2
+ from astToolkit import Be, Grab, identifierDotAttribute, Make, NodeChanger, NodeTourist, Then
3
+ from astToolkit.containers import LedgerOfImports
4
+ from astToolkit.transformationTools import write_astModule
3
5
  from hunterMakesPy import raiseIfNone
4
6
  from mapFolding import packageSettings
5
7
  from mapFolding.someAssemblyRequired import defaultA007822, IfThis
6
8
  from mapFolding.someAssemblyRequired.A007822.A007822rawMaterials import ExprCallFilterAsymmetricFoldsState
7
- from mapFolding.someAssemblyRequired.toolkitMakeModules import getModule, getPathFilename, write_astModule
9
+ from mapFolding.someAssemblyRequired.toolkitMakeModules import getModule, getPathFilename
8
10
  from pathlib import PurePath
9
11
  import ast
10
12
 
@@ -85,7 +87,7 @@ def addSymmetryCheckAsynchronous(astModule: ast.Module, identifierModule: str, i
85
87
 
86
88
  pathFilename: PurePath = getPathFilename(packageSettings.pathPackage, logicalPathInfix, identifierModule)
87
89
 
88
- write_astModule(astModule, pathFilename, packageSettings.identifierPackage)
90
+ write_astModule(astModule, pathFilename, identifierPackage=packageSettings.identifierPackage)
89
91
 
90
92
  return pathFilename
91
93
 
@@ -1,6 +1,8 @@
1
1
  """addSymmetryCheck."""
2
2
  from astToolkit import (
3
- Be, Grab, identifierDotAttribute, LedgerOfImports, NodeChanger, NodeTourist, parsePathFilename2astModule, Then)
3
+ Be, Grab, identifierDotAttribute, Make, NodeChanger, NodeTourist, parsePathFilename2astModule, Then)
4
+ from astToolkit.containers import LedgerOfImports
5
+ from astToolkit.transformationTools import write_astModule
4
6
  from hunterMakesPy import raiseIfNone
5
7
  from mapFolding import packageSettings
6
8
  from mapFolding.someAssemblyRequired import default, defaultA007822, IfThis
@@ -8,7 +10,7 @@ from mapFolding.someAssemblyRequired.A007822.A007822rawMaterials import (
8
10
  A007822adjustFoldsTotal, A007822incrementCount, FunctionDef_filterAsymmetricFolds)
9
11
  from mapFolding.someAssemblyRequired.makingModules_count import makeTheorem2, numbaOnTheorem2, trimTheorem2
10
12
  from mapFolding.someAssemblyRequired.makingModules_doTheNeedful import makeInitializeState
11
- from mapFolding.someAssemblyRequired.toolkitMakeModules import getModule, getPathFilename, write_astModule
13
+ from mapFolding.someAssemblyRequired.toolkitMakeModules import getModule, getPathFilename
12
14
  from pathlib import PurePath
13
15
  import ast
14
16
 
@@ -43,7 +45,21 @@ def addSymmetryCheck(astModule: ast.Module, identifierModule: str, identifierCal
43
45
 
44
46
  pathFilename: PurePath = getPathFilename(packageSettings.pathPackage, logicalPathInfix, identifierModule)
45
47
 
46
- write_astModule(astModule, pathFilename, packageSettings.identifierPackage)
48
+ write_astModule(astModule, pathFilename, identifierPackage=packageSettings.identifierPackage)
49
+
50
+ return pathFilename
51
+
52
+ def _numbaOnTheorem2(astModule: ast.Module, identifierModule: str, identifierCallable: str | None = None, logicalPathInfix: identifierDotAttribute | None = None, sourceCallableDispatcher: str | None = None) -> PurePath:
53
+ pathFilename: PurePath = numbaOnTheorem2(astModule, identifierModule, identifierCallable, logicalPathInfix, sourceCallableDispatcher)
54
+ astModule = parsePathFilename2astModule(pathFilename)
55
+
56
+ NodeChanger(Be.AnnAssign.valueIs(IfThis.isAttributeNamespaceIdentifier(defaultA007822['variable']['stateInstance'], 'indices'))
57
+ , lambda node: Grab.valueAttribute(Then.replaceWith(Make.Call(Make.Name('List'), [raiseIfNone(node.value)])))(node)
58
+ ).visit(astModule)
59
+
60
+ astModule.body.insert(0, Make.ImportFrom('numba.typed', [Make.alias('List')]))
61
+
62
+ write_astModule(astModule, pathFilename, identifierPackage=packageSettings.identifierPackage)
47
63
 
48
64
  return pathFilename
49
65
 
@@ -55,18 +71,18 @@ def makeA007822Modules() -> None:
55
71
 
56
72
  astModule = getModule(logicalPathInfix=defaultA007822['logicalPath']['synthetic'], identifierModule=defaultA007822['module']['algorithm'])
57
73
  makeInitializeState(astModule, defaultA007822['module']['initializeState']
58
- , defaultA007822['function']['initializeState'], defaultA007822['logicalPath']['synthetic'], None)
74
+ , defaultA007822['function']['initializeState'], defaultA007822['logicalPath']['synthetic'], None, identifiers=defaultA007822)
59
75
 
60
76
  astModule = getModule(logicalPathInfix=defaultA007822['logicalPath']['synthetic'], identifierModule=defaultA007822['module']['algorithm'])
61
77
  pathFilename = makeTheorem2(astModule, 'theorem2', defaultA007822['function']['counting']
62
- , defaultA007822['logicalPath']['synthetic'], defaultA007822['function']['dispatcher'])
78
+ , defaultA007822['logicalPath']['synthetic'], defaultA007822['function']['dispatcher'], identifiers=defaultA007822)
63
79
 
64
80
  astModule = parsePathFilename2astModule(pathFilename)
65
81
  pathFilename = trimTheorem2(astModule, 'theorem2Trimmed', defaultA007822['function']['counting']
66
82
  , defaultA007822['logicalPath']['synthetic'], defaultA007822['function']['dispatcher'])
67
83
 
68
84
  astModule = parsePathFilename2astModule(pathFilename)
69
- pathFilename = numbaOnTheorem2(astModule, 'theorem2Numba', defaultA007822['function']['counting']
85
+ pathFilename = _numbaOnTheorem2(astModule, 'theorem2Numba', defaultA007822['function']['counting']
70
86
  , defaultA007822['logicalPath']['synthetic'], defaultA007822['function']['dispatcher'])
71
87
 
72
88
  if __name__ == '__main__':