mapFolding 0.2.0__tar.gz → 0.2.2__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 (39) hide show
  1. {mapfolding-0.2.0 → mapfolding-0.2.2}/PKG-INFO +9 -46
  2. {mapfolding-0.2.0 → mapfolding-0.2.2}/README.md +8 -45
  3. mapfolding-0.2.2/mapFolding/__init__.py +13 -0
  4. mapfolding-0.2.2/mapFolding/babbage.py +30 -0
  5. mapfolding-0.2.2/mapFolding/beDRY.py +270 -0
  6. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/lovelace.py +41 -17
  7. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/oeis.py +59 -35
  8. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/hunterNumba.py +44 -44
  9. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/lunnan.py +5 -5
  10. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/lunnanNumpy.py +4 -4
  11. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/lunnanWhile.py +5 -5
  12. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/rotatedEntryPoint.py +68 -68
  13. mapfolding-0.2.2/mapFolding/reference/total_countPlus1vsPlusN.py +211 -0
  14. mapfolding-0.2.2/mapFolding/startHere.py +68 -0
  15. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/theSSOT.py +6 -1
  16. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/PKG-INFO +9 -46
  17. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/SOURCES.txt +1 -1
  18. {mapfolding-0.2.0 → mapfolding-0.2.2}/pyproject.toml +3 -2
  19. {mapfolding-0.2.0 → mapfolding-0.2.2}/tests/conftest.py +95 -37
  20. {mapfolding-0.2.0 → mapfolding-0.2.2}/tests/test_oeis.py +25 -26
  21. {mapfolding-0.2.0 → mapfolding-0.2.2}/tests/test_other.py +43 -9
  22. mapfolding-0.2.2/tests/test_tasks.py +31 -0
  23. mapfolding-0.2.0/mapFolding/__init__.py +0 -21
  24. mapfolding-0.2.0/mapFolding/babbage.py +0 -12
  25. mapfolding-0.2.0/mapFolding/beDRY.py +0 -219
  26. mapfolding-0.2.0/mapFolding/importPackages.py +0 -5
  27. mapfolding-0.2.0/mapFolding/startHere.py +0 -54
  28. mapfolding-0.2.0/tests/test_tasks.py +0 -18
  29. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/JAX/lunnanJAX.py +0 -0
  30. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/JAX/taskJAX.py +0 -0
  31. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/benchmarks/benchmarking.py +0 -0
  32. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/benchmarks/test_benchmarks.py +0 -0
  33. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding/reference/irvineJavaPort.py +0 -0
  34. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/dependency_links.txt +0 -0
  35. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/entry_points.txt +0 -0
  36. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/requires.txt +0 -0
  37. {mapfolding-0.2.0 → mapfolding-0.2.2}/mapFolding.egg-info/top_level.txt +0 -0
  38. {mapfolding-0.2.0 → mapfolding-0.2.2}/setup.cfg +0 -0
  39. {mapfolding-0.2.0 → mapfolding-0.2.2}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mapFolding
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Algorithm(s) for counting distinct ways to fold a map (or a strip of stamps)
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  Project-URL: homepage, https://github.com/hunterhogan/mapFolding
@@ -32,11 +32,11 @@ from mapFolding import countFolds
32
32
  foldsTotal = countFolds( [2,10] )
33
33
  ```
34
34
 
35
- The directory `mapFolding/reference` has
35
+ The directory [mapFolding/reference](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference) has
36
36
 
37
37
  - a verbatim transcription of the "procedure" published in _The Computer Journal_,
38
38
  - multiple referential versions of the procedure with explanatory comments including
39
- - `hunterNumba.py` a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
39
+ - [hunterNumba.py](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference), a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
40
40
  - miscellaneous notes.
41
41
 
42
42
  [![Python Tests](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml/badge.svg)](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml)
@@ -97,13 +97,13 @@ Cache cleared from C:\apps\mapFolding\mapFolding\.cache
97
97
 
98
98
  ### The typo-laden algorithm published in 1971
99
99
 
100
- The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
100
+ The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
101
101
 
102
- In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
102
+ In [`foldings.txt`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
103
103
 
104
104
  ### Java implementation(s) and improvements
105
105
 
106
- [archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
106
+ [archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
107
107
 
108
108
  ```java
109
109
  /**
@@ -122,49 +122,12 @@ In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text tran
122
122
 
123
123
  ~~This caused my neurosis:~~ I enjoyed the following video, which is what introduced me to map folding.
124
124
 
125
- "How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
125
+ "How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
126
126
 
127
127
  [![How Many Ways Can You Fold a Map?](https://i.ytimg.com/vi/sfH9uIY3ln4/hq720.jpg)](https://www.youtube.com/watch?v=sfH9uIY3ln4)
128
128
 
129
- ## Install this package
130
-
131
- ### From Github
132
-
133
- ```sh
134
- pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
135
- ```
136
-
137
- ### From a local directory
138
-
139
- #### Windows
140
-
141
- ```powershell
142
- git clone https://github.com/hunterhogan/mapFolding.git \path\to\mapFolding
143
- pip install mapFolding@file:\path\to\mapFolding
144
- ```
145
-
146
- #### POSIX
147
-
148
- ```bash
149
- git clone https://github.com/hunterhogan/mapFolding.git /path/to/mapFolding
150
- pip install mapFolding@file:/path/to/mapFolding
151
- ```
152
-
153
- ## Install updates
154
-
155
- ```sh
156
- pip install --upgrade mapFolding@git+https://github.com/hunterhogan/mapFolding.git
157
- ```
158
-
159
- ## Creating a virtual environment before installation
160
-
161
- You can isolate `mapFolding` in a virtual environment. For example, use the following commands to create a directory for the virtual environment, activate the virtual environment, and install the package. In the future, you will likely need to activate the virtual environment before using `mapFolding` again. From the command line, in a directory you want to install in.
129
+ ## Installation
162
130
 
163
131
  ```sh
164
- py -m venv mapFolding
165
- cd mapFolding
166
- cd Scripts
167
- activate
168
- cd ..
169
- pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
132
+ pip install mapFolding
170
133
  ```
@@ -7,11 +7,11 @@ from mapFolding import countFolds
7
7
  foldsTotal = countFolds( [2,10] )
8
8
  ```
9
9
 
10
- The directory `mapFolding/reference` has
10
+ The directory [mapFolding/reference](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference) has
11
11
 
12
12
  - a verbatim transcription of the "procedure" published in _The Computer Journal_,
13
13
  - multiple referential versions of the procedure with explanatory comments including
14
- - `hunterNumba.py` a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
14
+ - [hunterNumba.py](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference), a one-size-fits-all, self-contained, reasonably fast, contemporary algorithm that is nevertheless infected by _noobaceae ignorancium_, and
15
15
  - miscellaneous notes.
16
16
 
17
17
  [![Python Tests](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml/badge.svg)](https://github.com/hunterhogan/mapFolding/actions/workflows/unittests.yml)
@@ -72,13 +72,13 @@ Cache cleared from C:\apps\mapFolding\mapFolding\.cache
72
72
 
73
73
  ### The typo-laden algorithm published in 1971
74
74
 
75
- The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
75
+ The full paper, W. F. Lunnon, Multi-dimensional map-folding, _The Computer Journal_, Volume 14, Issue 1, 1971, Pages 75–80, [https://doi.org/10.1093/comjnl/14.1.75](https://doi.org/10.1093/comjnl/14.1.75) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Lunnon.bibtex) citation) is available at the DOI link. (As of 3 January 2025, the paper is a PDF of images, not text, and can be accessed without cost or login.)
76
76
 
77
- In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
77
+ In [`foldings.txt`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.txt), you can find a text transcription of the algorithm as it was printed in 1971. In [`foldings.AA`](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/reference/foldings.AA), I have corrected obvious transcription errors, documented with comments, and I have reformatted line breaks and indentation. For contemporary readers, the result is likely easier to read than the text transcription or the original paper are easy to read. This is especially true if you view the document with semantic highlighting, such as with [Algol 60 syntax highlighter](https://github.com/PolariTOON/language-algol60).
78
78
 
79
79
  ### Java implementation(s) and improvements
80
80
 
81
- [archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
81
+ [archmageirvine](https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java) ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/jOEIS.bibtex) citation) says about the Java code:
82
82
 
83
83
  ```java
84
84
  /**
@@ -97,49 +97,12 @@ In [`foldings.txt`](mapFolding/reference/foldings.txt), you can find a text tran
97
97
 
98
98
  ~~This caused my neurosis:~~ I enjoyed the following video, which is what introduced me to map folding.
99
99
 
100
- "How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
100
+ "How Many Ways Can You Fold a Map?" by Physics for the Birds, 2024 November 13 ([BibTex](https://github.com/hunterhogan/mapFolding/blob/main/mapFolding/citations/Physics_for_the_Birds.bibtex) citation)
101
101
 
102
102
  [![How Many Ways Can You Fold a Map?](https://i.ytimg.com/vi/sfH9uIY3ln4/hq720.jpg)](https://www.youtube.com/watch?v=sfH9uIY3ln4)
103
103
 
104
- ## Install this package
105
-
106
- ### From Github
107
-
108
- ```sh
109
- pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
110
- ```
111
-
112
- ### From a local directory
113
-
114
- #### Windows
115
-
116
- ```powershell
117
- git clone https://github.com/hunterhogan/mapFolding.git \path\to\mapFolding
118
- pip install mapFolding@file:\path\to\mapFolding
119
- ```
120
-
121
- #### POSIX
122
-
123
- ```bash
124
- git clone https://github.com/hunterhogan/mapFolding.git /path/to/mapFolding
125
- pip install mapFolding@file:/path/to/mapFolding
126
- ```
127
-
128
- ## Install updates
129
-
130
- ```sh
131
- pip install --upgrade mapFolding@git+https://github.com/hunterhogan/mapFolding.git
132
- ```
133
-
134
- ## Creating a virtual environment before installation
135
-
136
- You can isolate `mapFolding` in a virtual environment. For example, use the following commands to create a directory for the virtual environment, activate the virtual environment, and install the package. In the future, you will likely need to activate the virtual environment before using `mapFolding` again. From the command line, in a directory you want to install in.
104
+ ## Installation
137
105
 
138
106
  ```sh
139
- py -m venv mapFolding
140
- cd mapFolding
141
- cd Scripts
142
- activate
143
- cd ..
144
- pip install mapFolding@git+https://github.com/hunterhogan/mapFolding.git
107
+ pip install mapFolding
145
108
  ```
@@ -0,0 +1,13 @@
1
+ from .theSSOT import *
2
+ from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
3
+ from .beDRY import getTaskDivisions, makeConnectionGraph, outfitFoldings, setCPUlimit
4
+ from .beDRY import getLeavesTotal, parseDimensions, validateListDimensions
5
+ from .startHere import countFolds
6
+ from .oeis import oeisIDfor_n, getOEISids, clearOEIScache
7
+
8
+ __all__ = [
9
+ 'clearOEIScache',
10
+ 'countFolds',
11
+ 'getOEISids',
12
+ 'oeisIDfor_n',
13
+ ]
@@ -0,0 +1,30 @@
1
+ from mapFolding.lovelace import countFoldsCompiled
2
+ from numpy import integer
3
+ from numpy.typing import NDArray
4
+ from typing import Any, Tuple
5
+ import numba
6
+ import numpy
7
+
8
+ @numba.jit(cache=True)
9
+ def _countFolds(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], mapShape: Tuple[int, ...], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]) -> int:
10
+ """
11
+ What in tarnation is this stupid module and function?
12
+
13
+ - This function is not in the same module as `countFolds` so that we can delay Numba just-in-time (jit) compilation of this function and the finalization of its settings until we are ready.
14
+ - This function is not in the same module as `countFoldsCompiled`, which is the function that does the hard, so that we can delay `numba.jit` compilation of `countFoldsCompiled`.
15
+ - `countFoldsCompiled` is not merely "jitted", it is super jitted, which makes it too arrogant to talk to plebian Python functions. It will, however, reluctantly talk to basic jitted functions.
16
+ - The function in this module is jitted, so it can talk to `countFoldsCompiled`, and because it isn't so arrogant, it will talk to the low-class `countFolds` with only a few restrictions, such as:
17
+ - No `TypedDict`
18
+ - No Python v 3.13
19
+ - The plebs must clean up their own memory problems
20
+ - No oversized integers
21
+ - No global variables, only global constants
22
+ - They don't except pleb nonlocal variables either
23
+ - Python "class": they are all inferior to a jit
24
+ - No `**kwargs`
25
+ - and just a few dozen-jillion other things.
26
+
27
+ """
28
+ # TODO learn if I really must change this jitted function to get the super jit to recompile
29
+ # print('babbage')
30
+ return countFoldsCompiled(connectionGraph, foldsTotal, my, gapsWhere, the, track)
@@ -0,0 +1,270 @@
1
+ """A relatively stable API for oft-needed functionality."""
2
+ from mapFolding import intInnit, defineConcurrencyLimit, oopsieKwargsie
3
+ from mapFolding import indexMy, indexThe, indexTrack, computationState
4
+ from mapFolding import dtypeDefault, dtypeLarge, dtypeSmall
5
+ from typing import Any, List, Optional, Sequence, Type, Union
6
+ import numpy
7
+ import numba
8
+ from numpy.typing import NDArray
9
+ from numpy import integer
10
+ import sys
11
+ import operator
12
+
13
+ def getLeavesTotal(listDimensions: Sequence[int]) -> int:
14
+ """
15
+ How many leaves are in the map.
16
+
17
+ Parameters:
18
+ listDimensions: A list of integers representing dimensions.
19
+
20
+ Returns:
21
+ productDimensions: The product of all positive integer dimensions.
22
+ """
23
+ listNonNegative = parseDimensions(listDimensions, 'listDimensions')
24
+ listPositive = [dimension for dimension in listNonNegative if dimension > 0]
25
+
26
+ if not listPositive:
27
+ return 0
28
+ else:
29
+ productDimensions = 1
30
+ for dimension in listPositive:
31
+ if dimension > sys.maxsize // productDimensions:
32
+ raise OverflowError(f"I received {dimension=} in {listDimensions=}, but the product of the dimensions exceeds the maximum size of an integer on this system.")
33
+ productDimensions *= dimension
34
+
35
+ return productDimensions
36
+
37
+ def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrencyLimit: int, CPUlimit: Optional[Union[bool, float, int]], listDimensions: Sequence[int]):
38
+ """
39
+ Determines whether or how to divide the computation into tasks.
40
+
41
+ Parameters
42
+ ----------
43
+ computationDivisions (None):
44
+ Specifies how to divide computations:
45
+ - None: no division of the computation into tasks; sets task divisions to 0
46
+ - int: direct set the number of task divisions; cannot exceed the map's total leaves
47
+ - "maximum": divides into `leavesTotal`-many `taskDivisions`
48
+ - "cpu": limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`
49
+ concurrencyLimit:
50
+ Maximum number of concurrent tasks allowed
51
+ listDimensions: for error reporting
52
+ CPUlimit: for error reporting
53
+
54
+ Returns
55
+ -------
56
+ taskDivisions:
57
+
58
+ Raises
59
+ ------
60
+ ValueError
61
+ If computationDivisions is an unsupported type or if resulting task divisions exceed total leaves
62
+
63
+ Notes
64
+ -----
65
+ Task divisions cannot exceed total leaves to prevent duplicate counting of folds.
66
+ """
67
+ if not computationDivisions:
68
+ return 0
69
+ else:
70
+ leavesTotal = getLeavesTotal(listDimensions)
71
+ if isinstance(computationDivisions, int):
72
+ taskDivisions = computationDivisions
73
+ elif isinstance(computationDivisions, str):
74
+ computationDivisions = computationDivisions.lower()
75
+ if computationDivisions == "maximum":
76
+ taskDivisions = leavesTotal
77
+ elif computationDivisions == "cpu":
78
+ taskDivisions = min(concurrencyLimit, leavesTotal)
79
+ else:
80
+ raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
81
+
82
+ if taskDivisions > leavesTotal:
83
+ 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.\n\nFor those parameters, I received {computationDivisions=}, {CPUlimit=}, and {listDimensions=}.\n\nPotential solutions: get a different hobby or set `computationDivisions` to a different value.")
84
+
85
+ return taskDivisions
86
+
87
+ def makeConnectionGraph(listDimensions: Sequence[int], **keywordArguments: Optional[Type]) -> NDArray[integer[Any]]:
88
+ """
89
+ Constructs a multi-dimensional connection graph representing the connections between the leaves of a map with the given dimensions.
90
+ Also called a Cartesian product decomposition or dimensional product mapping.
91
+
92
+ Parameters:
93
+ listDimensions: A sequence of integers representing the dimensions of the map.
94
+ Returns:
95
+ connectionGraph: A 3D numpy array with shape of (dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1).
96
+ """
97
+ datatype = keywordArguments.get('datatype', dtypeDefault)
98
+ mapShape = validateListDimensions(listDimensions)
99
+ leavesTotal = getLeavesTotal(mapShape)
100
+ arrayDimensions = numpy.array(mapShape, dtype=datatype)
101
+ dimensionsTotal = len(arrayDimensions)
102
+
103
+ # Step 1: find the cumulative product of the map's dimensions
104
+ cumulativeProduct = numpy.multiply.accumulate([1] + mapShape, dtype=datatype)
105
+
106
+ # Step 2: create a coordinate system
107
+ coordinateSystem = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1), dtype=datatype)
108
+
109
+ for dimension1ndex in range(1, dimensionsTotal + 1):
110
+ for leaf1ndex in range(1, leavesTotal + 1):
111
+ coordinateSystem[dimension1ndex, leaf1ndex] = (
112
+ ((leaf1ndex - 1) // cumulativeProduct[dimension1ndex - 1]) %
113
+ arrayDimensions[dimension1ndex - 1] + 1
114
+ )
115
+
116
+ # Step 3: create and fill the connection graph
117
+ connectionGraph = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1), dtype=datatype)
118
+
119
+ for dimension1ndex in range(1, dimensionsTotal + 1):
120
+ for activeLeaf1ndex in range(1, leavesTotal + 1):
121
+ for connectee1ndex in range(1, activeLeaf1ndex + 1):
122
+ # Base coordinate conditions
123
+ isFirstCoord = coordinateSystem[dimension1ndex, connectee1ndex] == 1
124
+ isLastCoord = coordinateSystem[dimension1ndex, connectee1ndex] == arrayDimensions[dimension1ndex - 1]
125
+ exceedsActive = connectee1ndex + cumulativeProduct[dimension1ndex - 1] > activeLeaf1ndex
126
+
127
+ # Parity check
128
+ isEvenParity = (coordinateSystem[dimension1ndex, activeLeaf1ndex] & 1) == \
129
+ (coordinateSystem[dimension1ndex, connectee1ndex] & 1)
130
+
131
+ # Determine connection value
132
+ if (isEvenParity and isFirstCoord) or (not isEvenParity and (isLastCoord or exceedsActive)):
133
+ connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
134
+ elif isEvenParity and not isFirstCoord:
135
+ connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex - cumulativeProduct[dimension1ndex - 1]
136
+ elif not isEvenParity and not (isLastCoord or exceedsActive):
137
+ connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex + cumulativeProduct[dimension1ndex - 1]
138
+ else:
139
+ connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
140
+
141
+ return connectionGraph
142
+
143
+ def makeDataContainer(shape, datatype: Optional[Type] = None):
144
+ """Create a container, probably numpy.ndarray, with the given shape and datatype."""
145
+ if datatype is None:
146
+ datatype = dtypeDefault
147
+ return numpy.zeros(shape, dtype=datatype)
148
+
149
+ def outfitFoldings(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Type]) -> computationState:
150
+ """
151
+ Initializes and configures the computation state for map folding computations.
152
+
153
+ Parameters
154
+ ----------
155
+ listDimensions:
156
+ The dimensions of the map to be folded
157
+ computationDivisions (None):
158
+ Specifies how to divide the computation tasks
159
+ CPUlimit (None):
160
+ Limits the CPU usage for computations
161
+
162
+ Returns
163
+ -------
164
+ computationState
165
+ An initialized computation state containing:
166
+ - connectionGraph: Graph representing connections in the map
167
+ - foldsTotal: Array tracking total folds
168
+ - mapShape: Validated and sorted dimensions of the map
169
+ - my: Array for internal state tracking
170
+ - gapsWhere: Array tracking gap positions
171
+ - the: Static settings and metadata
172
+ - track: Array for tracking computation progress
173
+ """
174
+ datatypeDefault = keywordArguments.get('datatypeDefault', dtypeDefault)
175
+ datatypeLarge = keywordArguments.get('datatypeLarge', dtypeLarge)
176
+
177
+ the = makeDataContainer(len(indexThe), datatypeDefault)
178
+
179
+ mapShape = tuple(sorted(validateListDimensions(listDimensions)))
180
+ the[indexThe.leavesTotal] = getLeavesTotal(mapShape)
181
+ the[indexThe.dimensionsTotal] = len(mapShape)
182
+ concurrencyLimit = setCPUlimit(CPUlimit)
183
+ the[indexThe.taskDivisions] = getTaskDivisions(computationDivisions, concurrencyLimit, CPUlimit, listDimensions)
184
+
185
+ stateInitialized = computationState(
186
+ connectionGraph = makeConnectionGraph(mapShape, datatype=datatypeDefault),
187
+ foldsTotal = makeDataContainer(the[indexThe.leavesTotal], datatypeLarge),
188
+ mapShape = mapShape,
189
+ my = makeDataContainer(len(indexMy), datatypeLarge),
190
+ gapsWhere = makeDataContainer(int(the[indexThe.leavesTotal]) * int(the[indexThe.leavesTotal]) + 1, datatypeDefault),
191
+ the = the,
192
+ track = makeDataContainer((len(indexTrack), the[indexThe.leavesTotal] + 1), datatypeLarge)
193
+ )
194
+
195
+ stateInitialized['my'][indexMy.leaf1ndex.value] = 1
196
+
197
+ return stateInitialized
198
+
199
+ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'unnamed parameter') -> List[int]:
200
+ """
201
+ Parse and validate dimensions are non-negative integers.
202
+
203
+ Parameters:
204
+ dimensions: Sequence of integers representing dimensions
205
+ parameterName ('unnamed parameter'): Name of the parameter for error messages. Defaults to 'unnamed parameter'
206
+ Returns:
207
+ listNonNegative: List of validated non-negative integers
208
+ Raises:
209
+ ValueError: If any dimension is negative or if the list is empty
210
+ TypeError: If any element cannot be converted to integer (raised by intInnit)
211
+ """
212
+ listValidated = intInnit(dimensions, parameterName)
213
+ listNonNegative = []
214
+ for dimension in listValidated:
215
+ if dimension < 0:
216
+ raise ValueError(f"Dimension {dimension} must be non-negative")
217
+ listNonNegative.append(dimension)
218
+
219
+ if not listNonNegative:
220
+ raise ValueError("At least one dimension must be non-negative")
221
+
222
+ return listNonNegative
223
+
224
+ def setCPUlimit(CPUlimit: Union[bool, float, int, None]) -> int:
225
+ """Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
226
+
227
+ Parameters:
228
+ CPUlimit: whether and how to limit the CPU usage. See notes for details.
229
+ Returns:
230
+ concurrencyLimit: The actual concurrency limit that was set
231
+ Raises:
232
+ TypeError: If CPUlimit is not of the expected types
233
+
234
+ Limits on CPU usage `CPUlimit`:
235
+ - `False`, `None`, or `0`: No limits on CPU usage; uses all available CPUs. All other values will potentially limit CPU usage.
236
+ - `True`: Yes, limit the CPU usage; limits to 1 CPU.
237
+ - Integer `>= 1`: Limits usage to the specified number of CPUs.
238
+ - Decimal value (`float`) between 0 and 1: Fraction of total CPUs to use.
239
+ - Decimal value (`float`) between -1 and 0: Fraction of CPUs to *not* use.
240
+ - Integer `<= -1`: Subtract the absolute value from total CPUs.
241
+ """
242
+ if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
243
+ CPUlimit = oopsieKwargsie(CPUlimit)
244
+
245
+ concurrencyLimit = defineConcurrencyLimit(CPUlimit)
246
+ numba.set_num_threads(concurrencyLimit)
247
+
248
+ return concurrencyLimit
249
+
250
+ def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
251
+ """
252
+ Validates and sorts a sequence of at least two positive dimensions.
253
+
254
+ Parameters:
255
+ listDimensions: A sequence of integer dimensions to be validated.
256
+
257
+ Returns:
258
+ dimensionsValidSorted: A list, with at least two elements, of only positive integers.
259
+
260
+ Raises:
261
+ ValueError: If the input listDimensions is None.
262
+ NotImplementedError: If the resulting list of positive dimensions has fewer than two elements.
263
+ """
264
+ if not listDimensions:
265
+ raise ValueError(f"listDimensions is a required parameter.")
266
+ listNonNegative = parseDimensions(listDimensions, 'listDimensions')
267
+ dimensionsValid = [dimension for dimension in listNonNegative if dimension > 0]
268
+ if len(dimensionsValid) < 2:
269
+ 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/.")
270
+ return sorted(dimensionsValid)
@@ -1,27 +1,43 @@
1
+ """
2
+ The algorithm for counting folds.
3
+
4
+ Starting from established data structures, the algorithm initializes some baseline values. The initialization uses a loop that is not used after the first fold is counted.
5
+
6
+ After initialization, the folds are either counted sequentially or counted with inefficiently divided parallel tasks.
7
+
8
+ All three of these actions--initialization, sequential counting, and parallel counting--use nearly identical logic. Without Numba, all of the logic is in one function with exactly one additional
9
+ conditional statement for initialization and exactly one additional conditional statement for parallel counting.
10
+
11
+ Numba's just-in-time (jit) compiler, especially super jit, is capable of radically increasing throughput and dramatically reducing the size of the compiled code, especially by ejecting unused code.
12
+
13
+ The complexity of this module is due to me allegedly applying Numba's features. Allegedly.
14
+
15
+ (The flow starts with the last function.)
16
+ """
1
17
  from mapFolding import indexMy, indexThe, indexTrack
2
18
  from numpy import integer
3
19
  from numpy.typing import NDArray
4
- from typing import Any, Optional
20
+ from typing import Any, Tuple, Optional
5
21
  import numba
6
22
  import numpy
7
23
 
8
24
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
9
- def ifComputationDivisions(my: NDArray[integer[Any]], the: NDArray[integer[Any]]):
25
+ def ifComputationDivisions(my: NDArray[integer[Any]], the: NDArray[integer[Any]]) -> bool:
10
26
  if the[indexThe.taskDivisions.value] == 0:
11
27
  return True
12
28
  return my[indexMy.leaf1ndex.value] != the[indexThe.taskDivisions.value] or \
13
29
  (my[indexMy.leafConnectee.value] % the[indexThe.taskDivisions.value]) == my[indexMy.taskIndex.value]
14
30
 
15
31
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
16
- def insertUnconstrainedLeaf(my: NDArray[integer[Any]], the: NDArray[integer[Any]], Z0Z_initializeUnconstrainedLeaf: Optional[bool]):
17
- if Z0Z_initializeUnconstrainedLeaf:
32
+ def insertUnconstrainedLeaf(my: NDArray[integer[Any]], the: NDArray[integer[Any]], initializeUnconstrainedLeaf: Optional[bool]) -> bool:
33
+ if initializeUnconstrainedLeaf:
18
34
  return my[indexMy.dimensionsUnconstrained.value] == the[indexThe.dimensionsTotal.value]
19
35
  else:
20
36
  return False
21
37
 
22
38
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
23
- def initializationConditionUnconstrainedLeaf(my: NDArray[integer[Any]], Z0Z_initializeUnconstrainedLeaf: Optional[bool]):
24
- if Z0Z_initializeUnconstrainedLeaf is None or Z0Z_initializeUnconstrainedLeaf is False:
39
+ def initializationConditionUnconstrainedLeaf(my: NDArray[integer[Any]], initializeUnconstrainedLeaf: Optional[bool]) -> bool:
40
+ if initializeUnconstrainedLeaf is None or initializeUnconstrainedLeaf is False:
25
41
  return False
26
42
  else:
27
43
  if my[indexMy.gap1ndex.value] > 0:
@@ -30,7 +46,7 @@ def initializationConditionUnconstrainedLeaf(my: NDArray[integer[Any]], Z0Z_init
30
46
  return False
31
47
 
32
48
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
33
- def doWhile(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]], Z0Z_initializeUnconstrainedLeaf: Optional[bool] ):
49
+ def doWhile(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]], initializeUnconstrainedLeaf: Optional[bool]) -> Tuple[NDArray[integer[Any]], NDArray[integer[Any]], NDArray[integer[Any]], NDArray[integer[Any]]]:
34
50
  while my[indexMy.leaf1ndex.value] > 0:
35
51
  if my[indexMy.leaf1ndex.value] <= 1 or track[indexTrack.leafBelow.value, 0] == 1:
36
52
  if my[indexMy.leaf1ndex.value] > the[indexThe.leavesTotal.value]:
@@ -45,6 +61,7 @@ def doWhile(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[
45
61
  else:
46
62
  my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], my[indexMy.leaf1ndex.value]]
47
63
  while my[indexMy.leafConnectee.value] != my[indexMy.leaf1ndex.value]:
64
+ # NOTE This conditional check should only be in the parallel counting branch
48
65
  if ifComputationDivisions(my, the):
49
66
  gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.leafConnectee.value]
50
67
  if track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] == 0:
@@ -52,7 +69,8 @@ def doWhile(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[
52
69
  track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] += 1
53
70
  my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], track[indexTrack.leafBelow.value, my[indexMy.leafConnectee.value]]]
54
71
  my[indexMy.dimension1ndex.value] += 1
55
- if insertUnconstrainedLeaf(my, the, Z0Z_initializeUnconstrainedLeaf):
72
+ # NOTE This `if` statement and `while` loop should be absent from the code that does the counting
73
+ if insertUnconstrainedLeaf(my, the, initializeUnconstrainedLeaf):
56
74
  my[indexMy.indexLeaf.value] = 0
57
75
  while my[indexMy.indexLeaf.value] < my[indexMy.leaf1ndex.value]:
58
76
  gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.indexLeaf.value]
@@ -77,13 +95,16 @@ def doWhile(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[
77
95
  track[indexTrack.leafAbove.value, track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]] = my[indexMy.leaf1ndex.value]
78
96
  track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value]] = my[indexMy.gap1ndex.value]
79
97
  my[indexMy.leaf1ndex.value] += 1
80
- if initializationConditionUnconstrainedLeaf(my, Z0Z_initializeUnconstrainedLeaf):
98
+ # NOTE This check and break should be absent from the code that does the counting
99
+ if initializationConditionUnconstrainedLeaf(my, initializeUnconstrainedLeaf):
81
100
  break
82
101
  return foldsTotal, my, gapsWhere, track
83
102
 
84
103
  @numba.jit(parallel=True, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
85
- def doTaskIndices(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]):
86
-
104
+ def doTaskIndices(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]) -> NDArray[integer[Any]]:
105
+ """This is the only function with the `parallel=True` option.
106
+ Make a copy of the initialized state because all task divisions can start from this baseline.
107
+ Run the counting algorithm but with conditional execution of a few lines of code, so each task has an incomplete count that does not overlap with other tasks."""
87
108
  stateFoldsSubTotal = foldsTotal.copy()
88
109
  stateMy = my.copy()
89
110
  statePotentialGaps = gapsWhere.copy()
@@ -92,18 +113,17 @@ def doTaskIndices(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[in
92
113
  for indexSherpa in numba.prange(the[indexThe.taskDivisions.value]):
93
114
  my = stateMy.copy()
94
115
  my[indexMy.taskIndex.value] = indexSherpa
95
- foldsSubTotal, _1, _2, _3 = doWhile(connectionGraph, stateFoldsSubTotal.copy(), my, statePotentialGaps.copy(), the, stateTrack.copy(), Z0Z_initializeUnconstrainedLeaf=False)
116
+ foldsSubTotal, _1, _2, _3 = doWhile(connectionGraph, stateFoldsSubTotal.copy(), my, statePotentialGaps.copy(), the, stateTrack.copy(), initializeUnconstrainedLeaf=False)
96
117
 
97
118
  foldsTotal[indexSherpa] = foldsSubTotal[indexSherpa]
98
119
 
99
120
  return foldsTotal
100
121
 
101
122
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
102
- def countFoldsCompileBranch(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]],
103
- my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]],
104
- obviousFlagForNumba: bool):
123
+ def countFoldsCompileBranch(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]], obviousFlagForNumba: bool) -> NDArray[integer[Any]]:
124
+ """Allegedly, `obviousFlagForNumba` allows Numba to compile two versions: one for parallel execution and one leaner version for sequential execution."""
105
125
  if obviousFlagForNumba:
106
- foldsTotal, _1, _2, _3 = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, Z0Z_initializeUnconstrainedLeaf=False)
126
+ foldsTotal, _1, _2, _3 = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, initializeUnconstrainedLeaf=False)
107
127
  else:
108
128
  foldsTotal = doTaskIndices(connectionGraph, foldsTotal, my, gapsWhere, the, track)
109
129
 
@@ -111,11 +131,15 @@ def countFoldsCompileBranch(connectionGraph: NDArray[integer[Any]], foldsTotal:
111
131
 
112
132
  @numba.jit(parallel=False, _nrt=True, boundscheck=False, error_model='numpy', fastmath=True, forceinline=True, looplift=False, no_cfunc_wrapper=True, no_cpython_wrapper=True, nogil=True, nopython=True)
113
133
  def countFoldsCompiled(connectionGraph: NDArray[integer[Any]], foldsTotal: NDArray[integer[Any]], my: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]) -> int:
134
+ # ^ Receive the data structures.
114
135
 
115
- _0, my, gapsWhere, track = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, Z0Z_initializeUnconstrainedLeaf=True)
136
+ # Initialize baseline values primarily to eliminate the need for the logic of `insertUnconstrainedLeaf`
137
+ _0, my, gapsWhere, track = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, initializeUnconstrainedLeaf=True)
116
138
 
117
139
  obviousFlagForNumba = the[indexThe.taskDivisions.value] == int(False)
118
140
 
141
+ # Call the function that will branch to sequential or parallel counting
119
142
  foldsTotal = countFoldsCompileBranch(connectionGraph, foldsTotal, my, gapsWhere, the, track, obviousFlagForNumba)
120
143
 
144
+ # Return an `int` integer
121
145
  return numpy.sum(foldsTotal).item()