mapFolding 0.2.0__py3-none-any.whl → 0.2.2__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.
@@ -0,0 +1,211 @@
1
+ from numba import njit
2
+ import numpy
3
+
4
+ @njit(cache=True)
5
+ def foldings_plus_1(p: list[int], computationDivisions: int = 0, computationIndex: int = 0) -> int:
6
+ n: int = 1 # Total number of leaves
7
+ for dimension in p:
8
+ n *= dimension
9
+
10
+ d = len(p) # Number of dimensions
11
+ # Compute arrays P, C, D as per the algorithm
12
+ P = numpy.ones(d + 1, dtype=numpy.int64)
13
+ for i in range(1, d + 1):
14
+ P[i] = P[i - 1] * p[i - 1]
15
+
16
+ # C[i][m] holds the i-th coordinate of leaf m
17
+ C = numpy.zeros((d + 1, n + 1), dtype=numpy.int64)
18
+ for i in range(1, d + 1):
19
+ for m in range(1, n + 1):
20
+ C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i - 1] + 1
21
+
22
+ # D[i][l][m] computes the leaf connected to m in section i when inserting l
23
+ D = numpy.zeros((d + 1, n + 1, n + 1), dtype=numpy.int64)
24
+ for i in range(1, d + 1):
25
+ for l in range(1, n + 1):
26
+ for m in range(1, l + 1):
27
+ delta = C[i][l] - C[i][m]
28
+ if delta % 2 == 0:
29
+ # If delta is even
30
+ if C[i][m] == 1:
31
+ D[i][l][m] = m
32
+ else:
33
+ D[i][l][m] = m - P[i - 1]
34
+ else:
35
+ # If delta is odd
36
+ if C[i][m] == p[i - 1] or m + P[i - 1] > l:
37
+ D[i][l][m] = m
38
+ else:
39
+ D[i][l][m] = m + P[i - 1]
40
+ # Initialize arrays/lists
41
+ A = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf above leaf m
42
+ B = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf below leaf m
43
+ count = numpy.zeros(n + 1, dtype=numpy.int64) # Counts for potential gaps
44
+ gapter = numpy.zeros(n + 1, dtype=numpy.int64) # Indices for gap stack per leaf
45
+ gap = numpy.zeros(n * n + 1, dtype=numpy.int64) # Stack of potential gaps
46
+
47
+
48
+ # Initialize variables for backtracking
49
+ total_count = 0 # Total number of foldings
50
+ g = 0 # Gap index
51
+ l = 1 # Current leaf
52
+
53
+ # Start backtracking loop
54
+ while l > 0:
55
+ # If we have processed all leaves, increment total count
56
+ if l > n:
57
+ total_count += 1
58
+ else:
59
+ dd = 0 # Number of sections where leaf l is unconstrained
60
+ gg = g # Temporary gap index
61
+ g = gapter[l - 1] # Reset gap index for current leaf
62
+
63
+ # Count possible gaps for leaf l in each section
64
+ for i in range(1, d + 1):
65
+ if D[i][l][l] == l:
66
+ dd += 1
67
+ else:
68
+ m = D[i][l][l]
69
+ while m != l:
70
+ if computationDivisions == 0 or l != computationDivisions or m % computationDivisions == computationIndex:
71
+ gap[gg] = m
72
+ if count[m] == 0:
73
+ gg += 1
74
+ count[m] += 1
75
+ m = D[i][l][B[m]]
76
+
77
+ # If leaf l is unconstrained in all sections, it can be inserted anywhere
78
+ if dd == d:
79
+ for m in range(l):
80
+ gap[gg] = m
81
+ gg += 1
82
+
83
+ # Filter gaps that are common to all sections
84
+ for j in range(g, gg):
85
+ gap[g] = gap[j]
86
+ if count[gap[j]] == d - dd:
87
+ g += 1
88
+ count[gap[j]] = 0 # Reset count for next iteration
89
+
90
+ # Recursive backtracking steps
91
+ while l > 0 and g == gapter[l - 1]:
92
+ l -= 1
93
+ B[A[l]] = B[l]
94
+ A[B[l]] = A[l]
95
+
96
+ if l > 0:
97
+ g -= 1
98
+ A[l] = gap[g]
99
+ B[l] = B[A[l]]
100
+ B[A[l]] = l
101
+ A[B[l]] = l
102
+ gapter[l] = g # Save current gap index
103
+ l += 1 # Move to next leaf
104
+
105
+ return total_count
106
+
107
+ @njit(cache=True)
108
+ def foldings(p: list[int], computationDivisions: int = 0, computationIndex: int = 0) -> int:
109
+ n: int = 1 # Total number of leaves
110
+ for dimension in p:
111
+ n *= dimension
112
+
113
+ d = len(p) # Number of dimensions
114
+ # Compute arrays P, C, D as per the algorithm
115
+ P = numpy.ones(d + 1, dtype=numpy.int64)
116
+ for i in range(1, d + 1):
117
+ P[i] = P[i - 1] * p[i - 1]
118
+
119
+ # C[i][m] holds the i-th coordinate of leaf m
120
+ C = numpy.zeros((d + 1, n + 1), dtype=numpy.int64)
121
+ for i in range(1, d + 1):
122
+ for m in range(1, n + 1):
123
+ C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i - 1] + 1
124
+ # C[i][m] = ((m - 1) // P[i - 1]) % p[i - 1] + 1 # NOTE different, but either one works
125
+
126
+ # D[i][l][m] computes the leaf connected to m in section i when inserting l
127
+ D = numpy.zeros((d + 1, n + 1, n + 1), dtype=numpy.int64)
128
+ for i in range(1, d + 1):
129
+ for l in range(1, n + 1):
130
+ for m in range(1, l + 1):
131
+ delta = C[i][l] - C[i][m]
132
+ if delta % 2 == 0:
133
+ # If delta is even
134
+ if C[i][m] == 1:
135
+ D[i][l][m] = m
136
+ else:
137
+ D[i][l][m] = m - P[i - 1]
138
+ else:
139
+ # If delta is odd
140
+ if C[i][m] == p[i - 1] or m + P[i - 1] > l:
141
+ D[i][l][m] = m
142
+ else:
143
+ D[i][l][m] = m + P[i - 1]
144
+ # Initialize arrays/lists
145
+ A = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf above leaf m
146
+ B = numpy.zeros(n + 1, dtype=numpy.int64) # Leaf below leaf m
147
+ count = numpy.zeros(n + 1, dtype=numpy.int64) # Counts for potential gaps
148
+ gapter = numpy.zeros(n + 1, dtype=numpy.int64) # Indices for gap stack per leaf
149
+ gap = numpy.zeros(n * n + 1, dtype=numpy.int64) # Stack of potential gaps
150
+
151
+
152
+ # Initialize variables for backtracking
153
+ total_count = 0 # Total number of foldings
154
+ g = 0 # Gap index
155
+ l = 1 # Current leaf
156
+
157
+ # Start backtracking loop
158
+ while l > 0:
159
+ if l <= 1 or B[0] == 1: # NOTE different
160
+ # NOTE the above `if` statement encloses the the if/else block below
161
+ # NOTE these changes increase the throughput by more than an order of magnitude
162
+ if l > n:
163
+ total_count += n
164
+ else:
165
+ dd = 0 # Number of sections where leaf l is unconstrained
166
+ gg = gapter[l - 1] # Track possible gaps # NOTE different, but not important
167
+ g = gg # NOTE different, but not important
168
+
169
+ # Count possible gaps for leaf l in each section
170
+ for i in range(1, d + 1):
171
+ if D[i][l][l] == l:
172
+ dd += 1
173
+ else:
174
+ m = D[i][l][l]
175
+ while m != l:
176
+ if computationDivisions == 0 or l != computationDivisions or m % computationDivisions == computationIndex:
177
+ gap[gg] = m
178
+ if count[m] == 0:
179
+ gg += 1
180
+ count[m] += 1
181
+ m = D[i][l][B[m]]
182
+
183
+ # If leaf l is unconstrained in all sections, it can be inserted anywhere
184
+ if dd == d:
185
+ for m in range(l):
186
+ gap[gg] = m
187
+ gg += 1
188
+
189
+ # Filter gaps that are common to all sections
190
+ for j in range(g, gg):
191
+ gap[g] = gap[j]
192
+ if count[gap[j]] == d - dd:
193
+ g += 1
194
+ count[gap[j]] = 0 # Reset count for next iteration
195
+
196
+ # Recursive backtracking steps
197
+ while l > 0 and g == gapter[l - 1]:
198
+ l -= 1
199
+ B[A[l]] = B[l]
200
+ A[B[l]] = A[l]
201
+
202
+ if l > 0:
203
+ g -= 1
204
+ A[l] = gap[g]
205
+ B[l] = B[A[l]]
206
+ B[A[l]] = l
207
+ A[B[l]] = l
208
+ gapter[l] = g # Save current gap index
209
+ l += 1 # Move to next leaf
210
+
211
+ return total_count
mapFolding/startHere.py CHANGED
@@ -1,27 +1,38 @@
1
- from numpy import integer
2
- from numpy.typing import NDArray
3
- from typing import Any, Tuple
4
- import numba
5
- import numpy
6
1
  from mapFolding import outfitFoldings
7
- # from mapFolding.benchmarks.benchmarking import recordBenchmarks
8
- from typing import Optional, Union, Sequence, Type
2
+ from typing import Optional, Sequence, Type, Union
9
3
  import os
10
4
  import pathlib
11
5
 
12
- # TODO the current tests expect positional `listDimensions, computationDivisions`, so after restructuring you can arrange the parameters however you want.
13
- def countFolds(
14
- listDimensions: Sequence[int],
15
- computationDivisions: Optional[Union[int, str]] = None,
16
- CPUlimit: Optional[Union[int, float, bool]] = None,
17
- writeFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None,
18
- **keywordArguments: Optional[Type]
19
- ):
20
- """keywordArguments:
21
- dtypeDefault: Optional[Type]
22
- dtypeLarge: Optional[Type]
23
-
24
- writeFoldsTotal: path, filename, or pathFilename
6
+ def countFolds(listDimensions: Sequence[int], writeFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None, computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[int, float, bool]] = None, **keywordArguments: Optional[Type]) -> int:
7
+ """Count the total number of possible foldings for a given map dimensions.
8
+
9
+ Parameters:
10
+ listDimensions: List of integers representing the dimensions of the map to be folded.
11
+ writeFoldsTotal (None): Path or filename to write the total fold count.
12
+ If a directory is provided, creates a file with default name based on map dimensions.
13
+ computationDivisions (None):
14
+ Whether and how to divide the computational work. See notes for details.
15
+ CPUlimit (None): This is only relevant if there are `computationDivisions`: whether and how to limit the CPU usage. See notes for details.
16
+ **keywordArguments: Additional arguments including `dtypeDefault` and `dtypeLarge` for data type specifications.
17
+ Returns:
18
+ foldsTotal: Total number of distinct ways to fold a map of the given dimensions.
19
+
20
+ Computation divisions:
21
+ - None: no division of the computation into tasks; sets task divisions to 0
22
+ - int: direct set the number of task divisions; cannot exceed the map's total leaves
23
+ - "maximum": divides into `leavesTotal`-many `taskDivisions`
24
+ - "cpu": limits the divisions to the number of available CPUs, i.e. `concurrencyLimit`
25
+
26
+ Limits on CPU usage `CPUlimit`:
27
+ - `False`, `None`, or `0`: No limits on CPU usage; uses all available CPUs. All other values will potentially limit CPU usage.
28
+ - `True`: Yes, limit the CPU usage; limits to 1 CPU.
29
+ - Integer `>= 1`: Limits usage to the specified number of CPUs.
30
+ - Decimal value (`float`) between 0 and 1: Fraction of total CPUs to use.
31
+ - Decimal value (`float`) between -1 and 0: Fraction of CPUs to *not* use.
32
+ - Integer `<= -1`: Subtract the absolute value from total CPUs.
33
+
34
+ N.B.: You probably don't want to divide the computation into tasks.
35
+ If you want to compute a large `foldsTotal`, dividing the computation into tasks is usually a bad idea. Dividing the algorithm into tasks is inherently inefficient: efficient division into tasks means there would be no overlap in the work performed by each task. When dividing this algorithm, the amount of overlap is between 50% and 90% by all tasks: at least 50% of the work done by every task must be done by _all_ tasks. If you improve the computation time, it will only change by -10 to -50% depending on (at the very least) the ratio of the map dimensions and the number of leaves. If an undivided computation would take 10 hours on your computer, for example, the computation will still take at least 5 hours but you might reduce the time to 9 hours. Most of the time, however, you will increase the computation time. If logicalCores >= leavesTotal, it will probably be faster. If logicalCores <= 2 * leavesTotal, it will almost certainly be slower for all map dimensions.
25
36
  """
26
37
  stateUniversal = outfitFoldings(listDimensions, computationDivisions=computationDivisions, CPUlimit=CPUlimit, **keywordArguments)
27
38
 
@@ -33,6 +44,7 @@ def countFolds(
33
44
  pathFilenameFoldsTotal = pathFilenameFoldsTotal / filenameFoldsTotalDEFAULT
34
45
  pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
35
46
 
47
+ # NOTE Don't import a module with a numba.jit function until you want the function to compile and to freeze all settings for that function.
36
48
  from mapFolding.babbage import _countFolds
37
49
  foldsTotal = _countFolds(**stateUniversal)
38
50
  # foldsTotal = benchmarkSherpa(**stateUniversal)
@@ -42,12 +54,14 @@ def countFolds(
42
54
  pathFilenameFoldsTotal.write_text(str(foldsTotal))
43
55
  except Exception as ERRORmessage:
44
56
  print(ERRORmessage)
45
- print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
46
- print(f"{foldsTotal=}")
47
- print("\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
57
+ print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal")
48
58
 
49
59
  return foldsTotal
50
60
 
61
+ # from numpy import integer
62
+ # from numpy.typing import NDArray
63
+ # from typing import Any, Tuple
64
+ # from mapFolding.benchmarks.benchmarking import recordBenchmarks
51
65
  # @recordBenchmarks()
52
66
  # def benchmarkSherpa(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]]):
53
67
  # from mapFolding.babbage import _countFolds
mapFolding/theSSOT.py CHANGED
@@ -5,6 +5,10 @@ import numpy.typing
5
5
  import pathlib
6
6
  import sys
7
7
 
8
+ dtypeLarge = numpy.int64
9
+ dtypeDefault = dtypeLarge
10
+ dtypeSmall = dtypeDefault
11
+
8
12
  try:
9
13
  _pathModule = pathlib.Path(__file__).parent
10
14
  except NameError:
@@ -24,7 +28,8 @@ class EnumIndices(enum.IntEnum):
24
28
  return count
25
29
 
26
30
  def __index__(self) -> int:
27
- """Make the enum work with array indexing."""
31
+ """Adapt enum to the ultra-rare event of indexing a NumPy 'ndarray', which is not the
32
+ same as `array.array`. See NumPy.org; I think it will be very popular someday."""
28
33
  return self.value
29
34
 
30
35
  class indexMy(EnumIndices):
@@ -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
  ```
@@ -0,0 +1,28 @@
1
+ mapFolding/__init__.py,sha256=POxXTSE6Qu1G8k-YlSXHzbe-_JGuHk-L9p5SLRng4vA,439
2
+ mapFolding/babbage.py,sha256=3D1qcntoiJm2tqgbiCAMc7GpGA0SZTdGORNEnensyxk,1882
3
+ mapFolding/beDRY.py,sha256=ZrUXCNqnumTQ1XX8RT5gGdRr-a-o8QxEiGrerUxKtYg,12784
4
+ mapFolding/lovelace.py,sha256=qcyGpVEPP3n0r-RNrSUgPRP3yZJfBEfmok7jYMm1OI0,11473
5
+ mapFolding/oeis.py,sha256=_-fLGc1ybZ2eFxoiBrSmojMexeg6ROxtrLaBF2BzMn4,12144
6
+ mapFolding/startHere.py,sha256=RGdFoJJdrJ_0tmLIKZn1WnHP0NCZwvQG7C2p4EUHOe4,5034
7
+ mapFolding/theSSOT.py,sha256=yRW6aHyJxheL-Znk537LQA6xUPHz6FfoXY-0Ayh3Lsg,2178
8
+ mapFolding/JAX/lunnanJAX.py,sha256=xMZloN47q-MVfjdYOM1hi9qR4OnLq7qALmGLMraevQs,14819
9
+ mapFolding/JAX/taskJAX.py,sha256=yJNeH0rL6EhJ6ppnATHF0Zf81CDMC10bnPnimVxE1hc,20037
10
+ mapFolding/benchmarks/benchmarking.py,sha256=kv85F6V9pGhZvTOImArOuxyg5rywA_T6JLH_qFXM8BM,3018
11
+ mapFolding/benchmarks/test_benchmarks.py,sha256=c4ANeR3jgqpKXFoxDeZkmAHxSuenMwsjmrhKJ1_XPqY,3659
12
+ mapFolding/reference/hunterNumba.py,sha256=0giUyqAFzP-XKcq3Kz8wIWCK0BVFhjABVJ1s-w4Jhu0,7109
13
+ mapFolding/reference/irvineJavaPort.py,sha256=Sj-63Z-OsGuDoEBXuxyjRrNmmyl0d7Yz_XuY7I47Oyg,4250
14
+ mapFolding/reference/lunnan.py,sha256=XEcql_gxvCCghb6Or3qwmPbn4IZUbZTaSmw_fUjRxZE,5037
15
+ mapFolding/reference/lunnanNumpy.py,sha256=HqDgSwTOZA-G0oophOEfc4zs25Mv4yw2aoF1v8miOLk,4653
16
+ mapFolding/reference/lunnanWhile.py,sha256=7NY2IKO5XBgol0aWWF_Fi-7oTL9pvu_z6lB0TF1uVHk,4063
17
+ mapFolding/reference/rotatedEntryPoint.py,sha256=z0QyDQtnMvXNj5ntWzzJUQUMFm1-xHGLVhtYzwmczUI,11530
18
+ mapFolding/reference/total_countPlus1vsPlusN.py,sha256=usenM8Yn_G1dqlPl7NKKkcnbohBZVZBXTQRm2S3_EDA,8106
19
+ tests/__init__.py,sha256=PGYVr7r23gATgcvZ3Sfph9D_g1MVvhgzMNWXBs_9tmY,52
20
+ tests/conftest.py,sha256=VFYSd7-tHWd-LUKnTY24PIJhq9quP9S3sK2SYusNNog,12875
21
+ tests/test_oeis.py,sha256=vxnwO-cSR68htkyMh9QMVv-lvxBo6qlwPg1Rbx4JylY,7963
22
+ tests/test_other.py,sha256=cf8DbkZxm_DHNq9lkMe7auXye_XspruU9qiNZATkxr4,6930
23
+ tests/test_tasks.py,sha256=Nwe4iuSjwGZvsw5CXCcic7tkBxgM5JX9mrGZMDYhAwE,1785
24
+ mapFolding-0.2.2.dist-info/METADATA,sha256=BSCXcKZDhAtOWr8A5pZMG8bZnm5YBLf-6uMN9qs8JDk,5914
25
+ mapFolding-0.2.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
26
+ mapFolding-0.2.2.dist-info/entry_points.txt,sha256=F3OUeZR1XDTpoH7k3wXuRb3KF_kXTTeYhu5AGK1SiOQ,146
27
+ mapFolding-0.2.2.dist-info/top_level.txt,sha256=1gP2vFaqPwHujGwb3UjtMlLEGN-943VSYFR7V4gDqW8,17
28
+ mapFolding-0.2.2.dist-info/RECORD,,
tests/conftest.py CHANGED
@@ -3,31 +3,33 @@ Other test modules must not import directly from the package being tested."""
3
3
 
4
4
  # TODO learn how to run tests and coverage analysis without `env = ["NUMBA_DISABLE_JIT=1"]`
5
5
 
6
- from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Tuple, Type, Union
6
+ from typing import Any, Callable, Dict, Generator, List, Optional, Sequence, Set, Tuple, Type, Union
7
7
  import pathlib
8
8
  import pytest
9
9
  import random
10
+ import shutil
10
11
  import unittest.mock
11
-
12
- from mapFolding import clearOEIScache
13
- from mapFolding import countFolds, pathJobDEFAULT
12
+ import uuid
13
+ from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
14
+ from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
15
+ from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
16
+ from mapFolding import countFolds, pathJobDEFAULT, indexMy, indexThe, indexTrack
17
+ from mapFolding import defineConcurrencyLimit, intInnit, oopsieKwargsie
14
18
  from mapFolding import getLeavesTotal, parseDimensions, validateListDimensions
15
- from mapFolding.importPackages import makeTestSuiteConcurrencyLimit, defineConcurrencyLimit
16
- from mapFolding.importPackages import makeTestSuiteIntInnit, intInnit
17
- from mapFolding.importPackages import makeTestSuiteOopsieKwargsie, oopsieKwargsie
19
+ from mapFolding import getTaskDivisions, makeConnectionGraph, outfitFoldings, setCPUlimit
20
+ from mapFolding import oeisIDfor_n, getOEISids, clearOEIScache
21
+ from mapFolding.beDRY import makeDataContainer
18
22
  from mapFolding.oeis import OEIS_for_n
19
- from mapFolding.oeis import _formatFilenameCache
23
+ from mapFolding.oeis import _getFilenameOEISbFile
20
24
  from mapFolding.oeis import _getOEISidValues
21
25
  from mapFolding.oeis import _parseBFileOEIS
22
26
  from mapFolding.oeis import _validateOEISid
23
- from mapFolding.oeis import getOEISids
24
- from mapFolding.oeis import oeisIDfor_n
25
27
  from mapFolding.oeis import oeisIDsImplemented
26
28
  from mapFolding.oeis import settingsOEIS
27
29
 
28
30
  __all__ = [
29
31
  'OEIS_for_n',
30
- '_formatFilenameCache',
32
+ '_getFilenameOEISbFile',
31
33
  '_getOEISidValues',
32
34
  '_parseBFileOEIS',
33
35
  '_validateOEISid',
@@ -37,14 +39,20 @@ __all__ = [
37
39
  'expectSystemExit',
38
40
  'getLeavesTotal',
39
41
  'getOEISids',
42
+ 'indexThe',
43
+ 'getTaskDivisions',
40
44
  'intInnit',
45
+ 'makeConnectionGraph',
46
+ 'makeDataContainer',
41
47
  'makeTestSuiteConcurrencyLimit',
42
48
  'makeTestSuiteIntInnit',
43
49
  'makeTestSuiteOopsieKwargsie',
44
50
  'oeisIDfor_n',
45
51
  'oeisIDsImplemented',
46
52
  'oopsieKwargsie',
53
+ 'outfitFoldings',
47
54
  'parseDimensions',
55
+ 'setCPUlimit',
48
56
  'settingsOEIS',
49
57
  'standardCacheTest',
50
58
  'standardComparison',
@@ -91,6 +99,75 @@ def makeDictionaryFoldsTotalKnown() -> Dict[Tuple[int,...], int]:
91
99
 
92
100
  return dictionaryMapDimensionsToFoldsTotalKnown
93
101
 
102
+ """
103
+ Section: temporary paths and pathFilenames"""
104
+
105
+ # SSOT for test data paths
106
+ pathDataSamples = pathlib.Path("tests/dataSamples")
107
+ pathTempRoot = pathDataSamples / "tmp"
108
+
109
+ # The registrar maintains the register of temp files
110
+ registerOfTempFiles: Set[pathlib.Path] = set()
111
+
112
+ def addTempFileToRegister(path: pathlib.Path) -> None:
113
+ """The registrar adds a temp file to the register."""
114
+ registerOfTempFiles.add(path)
115
+
116
+ def cleanupTempFileRegister() -> None:
117
+ """The registrar cleans up temp files in the register."""
118
+ for pathTemp in sorted(registerOfTempFiles, reverse=True):
119
+ try:
120
+ if pathTemp.is_file():
121
+ pathTemp.unlink(missing_ok=True)
122
+ elif pathTemp.is_dir():
123
+ shutil.rmtree(pathTemp, ignore_errors=True)
124
+ except Exception as ERRORmessage:
125
+ print(f"Warning: Failed to clean up {pathTemp}: {ERRORmessage}")
126
+ registerOfTempFiles.clear()
127
+
128
+ @pytest.fixture(scope="session", autouse=True)
129
+ def setupTeardownTestData() -> Generator[None, None, None]:
130
+ """Auto-fixture to setup test data directories and cleanup after."""
131
+ pathDataSamples.mkdir(exist_ok=True)
132
+ pathTempRoot.mkdir(exist_ok=True)
133
+ yield
134
+ cleanupTempFileRegister()
135
+
136
+ @pytest.fixture
137
+ def pathTempTesting(request: pytest.FixtureRequest) -> pathlib.Path:
138
+ """Create a unique temp directory for each test function."""
139
+ # Sanitize test name for filesystem compatibility
140
+ sanitizedName = request.node.name.replace('[', '_').replace(']', '_').replace('/', '_')
141
+ uniqueDirectory = f"{sanitizedName}_{uuid.uuid4()}"
142
+ pathTemp = pathTempRoot / uniqueDirectory
143
+ pathTemp.mkdir(parents=True, exist_ok=True)
144
+
145
+ addTempFileToRegister(pathTemp)
146
+ return pathTemp
147
+
148
+ @pytest.fixture
149
+ def pathCacheTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
150
+ """Temporarily replace the OEIS cache directory with a test directory."""
151
+ from mapFolding import oeis as there_must_be_a_better_way
152
+ pathCacheOriginal = there_must_be_a_better_way._pathCache
153
+ there_must_be_a_better_way._pathCache = pathTempTesting
154
+ yield pathTempTesting
155
+ there_must_be_a_better_way._pathCache = pathCacheOriginal
156
+
157
+ @pytest.fixture
158
+ def pathFilenameBenchmarksTesting(pathTempTesting: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
159
+ """Temporarily replace the benchmarks directory with a test directory."""
160
+ from mapFolding.benchmarks import benchmarking
161
+ pathFilenameOriginal = benchmarking.pathFilenameRecordedBenchmarks
162
+ pathFilenameTest = pathTempTesting / "benchmarks.npy"
163
+ benchmarking.pathFilenameRecordedBenchmarks = pathFilenameTest
164
+ yield pathFilenameTest
165
+ benchmarking.pathFilenameRecordedBenchmarks = pathFilenameOriginal
166
+
167
+ @pytest.fixture
168
+ def pathFilenameFoldsTotalTesting(pathTempTesting: pathlib.Path) -> pathlib.Path:
169
+ return pathTempTesting.joinpath("foldsTotalTest.txt")
170
+
94
171
  """
95
172
  Section: Fixtures"""
96
173
 
@@ -150,28 +227,15 @@ def oeisID_1random() -> str:
150
227
  """Return one random valid OEIS ID."""
151
228
  return random.choice(oeisIDsImplemented)
152
229
 
153
- @pytest.fixture
154
- def pathCacheTesting(tmp_path: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
155
- """Temporarily replace the OEIS cache directory with a test directory."""
156
- from mapFolding import oeis as there_must_be_a_better_way
157
- pathCacheOriginal = there_must_be_a_better_way._pathCache
158
- there_must_be_a_better_way._pathCache = tmp_path
159
- yield tmp_path
160
- there_must_be_a_better_way._pathCache = pathCacheOriginal
161
-
162
- @pytest.fixture
163
- def pathBenchmarksTesting(tmp_path: pathlib.Path) -> Generator[pathlib.Path, Any, None]:
164
- """Temporarily replace the benchmarks directory with a test directory."""
165
- from mapFolding.benchmarks import benchmarking
166
- pathOriginal = benchmarking.pathFilenameRecordedBenchmarks
167
- pathTest = tmp_path / "benchmarks.npy"
168
- benchmarking.pathFilenameRecordedBenchmarks = pathTest
169
- yield pathTest
170
- benchmarking.pathFilenameRecordedBenchmarks = pathOriginal
171
-
172
230
  """
173
231
  Section: Standardized test structures"""
174
232
 
233
+ def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
234
+ """Format assertion message for any test comparison."""
235
+ return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
236
+ f"Expected: {expected}\n"
237
+ f"Got: {actual}")
238
+
175
239
  def standardComparison(expected: Any, functionTarget: Callable, *arguments: Any) -> None:
176
240
  """Template for tests expecting an error."""
177
241
  if type(expected) == Type[Exception]:
@@ -217,12 +281,6 @@ def expectSystemExit(expected: Union[str, int, Sequence[int]], functionTarget: C
217
281
  assert exitCode == expected, \
218
282
  f"Expected exit code {expected} but got {exitCode}"
219
283
 
220
- def formatTestMessage(expected: Any, actual: Any, functionName: str, *arguments: Any) -> str:
221
- """Format assertion message for any test comparison."""
222
- return (f"\nTesting: `{functionName}({', '.join(str(parameter) for parameter in arguments)})`\n"
223
- f"Expected: {expected}\n"
224
- f"Got: {actual}")
225
-
226
284
  def standardCacheTest(
227
285
  expected: Any,
228
286
  setupCacheFile: Optional[Callable[[pathlib.Path, str], None]],
@@ -237,7 +295,7 @@ def standardCacheTest(
237
295
  oeisID: OEIS ID to test
238
296
  pathCache: Temporary cache directory path
239
297
  """
240
- pathFilenameCache = pathCache / _formatFilenameCache.format(oeisID=oeisID)
298
+ pathFilenameCache = pathCache / _getFilenameOEISbFile(oeisID)
241
299
 
242
300
  # Setup cache file if provided
243
301
  if setupCacheFile: