mapFolding 0.3.7__py3-none-any.whl → 0.3.9__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 (47) hide show
  1. mapFolding/__init__.py +38 -0
  2. mapFolding/basecamp.py +55 -0
  3. mapFolding/beDRY.py +364 -0
  4. mapFolding/oeis.py +329 -0
  5. mapFolding/someAssemblyRequired/makeJob.py +62 -0
  6. mapFolding/someAssemblyRequired/synthesizeModuleJAX.py +29 -0
  7. someAssemblyRequired/synthesizeModuleJob.py → mapFolding/someAssemblyRequired/synthesizeModuleJobNumba.py +81 -27
  8. mapFolding/someAssemblyRequired/synthesizeModulesNumba.py +506 -0
  9. mapFolding/syntheticModules/__init__.py +3 -0
  10. syntheticModules/Initialize.py → mapFolding/syntheticModules/numba_countInitialize.py +5 -4
  11. syntheticModules/Parallel.py → mapFolding/syntheticModules/numba_countParallel.py +10 -5
  12. syntheticModules/Sequential.py → mapFolding/syntheticModules/numba_countSequential.py +5 -4
  13. mapFolding/syntheticModules/numba_doTheNeedful.py +33 -0
  14. mapFolding/theDao.py +214 -0
  15. mapFolding/theSSOT.py +269 -0
  16. mapFolding-0.3.9.dist-info/LICENSE +407 -0
  17. {mapFolding-0.3.7.dist-info → mapFolding-0.3.9.dist-info}/METADATA +9 -5
  18. mapFolding-0.3.9.dist-info/RECORD +40 -0
  19. mapFolding-0.3.9.dist-info/top_level.txt +2 -0
  20. tests/__init__.py +1 -0
  21. tests/conftest.py +224 -0
  22. tests/conftest_tmpRegistry.py +62 -0
  23. tests/conftest_uniformTests.py +53 -0
  24. tests/test_oeis.py +200 -0
  25. tests/test_other.py +258 -0
  26. tests/test_tasks.py +44 -0
  27. tests/test_types.py +5 -0
  28. benchmarks/benchmarking.py +0 -67
  29. citations/updateCitation.py +0 -238
  30. mapFolding-0.3.7.dist-info/RECORD +0 -25
  31. mapFolding-0.3.7.dist-info/top_level.txt +0 -5
  32. someAssemblyRequired/makeJob.py +0 -34
  33. someAssemblyRequired/synthesizeModules.py +0 -216
  34. syntheticModules/__init__.py +0 -4
  35. {reference → mapFolding/reference}/flattened.py +0 -0
  36. {reference → mapFolding/reference}/hunterNumba.py +0 -0
  37. {reference → mapFolding/reference}/irvineJavaPort.py +0 -0
  38. {reference → mapFolding/reference}/jax.py +0 -0
  39. {reference → mapFolding/reference}/lunnan.py +0 -0
  40. {reference → mapFolding/reference}/lunnanNumpy.py +0 -0
  41. {reference → mapFolding/reference}/lunnanWhile.py +0 -0
  42. {reference → mapFolding/reference}/rotatedEntryPoint.py +0 -0
  43. {reference → mapFolding/reference}/total_countPlus1vsPlusN.py +0 -0
  44. {someAssemblyRequired → mapFolding/someAssemblyRequired}/__init__.py +0 -0
  45. {someAssemblyRequired → mapFolding/someAssemblyRequired}/getLLVMforNoReason.py +0 -0
  46. {mapFolding-0.3.7.dist-info → mapFolding-0.3.9.dist-info}/WHEEL +0 -0
  47. {mapFolding-0.3.7.dist-info → mapFolding-0.3.9.dist-info}/entry_points.txt +0 -0
mapFolding/oeis.py ADDED
@@ -0,0 +1,329 @@
1
+ """Everything implementing the The Online Encyclopedia of Integer Sequences (OEIS);
2
+ _only_ things that implement _only_ the OEIS."""
3
+ from datetime import datetime, timedelta
4
+ from mapFolding import countFolds
5
+ from typing import TYPE_CHECKING, List, Callable, Dict, Final, Union, Any
6
+ import argparse
7
+ import pathlib
8
+ import random
9
+ import sys
10
+ import time
11
+ import urllib.request
12
+ import urllib.response
13
+
14
+ if TYPE_CHECKING:
15
+ from typing import TypedDict
16
+ else:
17
+ TypedDict = dict
18
+
19
+ """
20
+ Section: make `settingsOEIS`"""
21
+ class SettingsOEIS(TypedDict):
22
+ # I would prefer to load description dynamically from OEIS, but it's a pita for me
23
+ # to learn how to efficiently implement right now.
24
+ description: str
25
+ getMapShape: Callable[[int], List[int]]
26
+ valuesBenchmark: List[int]
27
+ valuesKnown: Dict[int, int]
28
+ valuesTestParallelization: List[int]
29
+ valuesTestValidation: List[int]
30
+ valueUnknown: int
31
+
32
+ settingsOEIShardcodedValues: Dict[str, Dict[str, Any]] = {
33
+ 'A001415': {
34
+ 'description': 'Number of ways of folding a 2 X n strip of stamps.',
35
+ 'getMapShape': lambda n: sorted([2, n]),
36
+ 'valuesBenchmark': [14],
37
+ 'valuesTestParallelization': [*range(3, 7)],
38
+ 'valuesTestValidation': [0, 1, random.randint(2, 9)],
39
+ },
40
+ 'A001416': {
41
+ 'description': 'Number of ways of folding a 3 X n strip of stamps.',
42
+ 'getMapShape': lambda n: sorted([3, n]),
43
+ 'valuesBenchmark': [9],
44
+ 'valuesTestParallelization': [*range(3, 5)],
45
+ 'valuesTestValidation': [0, 1, random.randint(2, 6)],
46
+ },
47
+ 'A001417': {
48
+ 'description': 'Number of ways of folding a 2 X 2 X ... X 2 n-dimensional map.',
49
+ 'getMapShape': lambda n: [2] * n,
50
+ 'valuesBenchmark': [6],
51
+ 'valuesTestParallelization': [*range(2, 4)],
52
+ 'valuesTestValidation': [0, 1, random.randint(2, 4)],
53
+ },
54
+ 'A195646': {
55
+ 'description': 'Number of ways of folding a 3 X 3 X ... X 3 n-dimensional map.',
56
+ 'getMapShape': lambda n: [3] * n,
57
+ 'valuesBenchmark': [3],
58
+ 'valuesTestParallelization': [*range(2, 3)],
59
+ 'valuesTestValidation': [0, 1, 2],
60
+ },
61
+ 'A001418': {
62
+ 'description': 'Number of ways of folding an n X n sheet of stamps.',
63
+ 'getMapShape': lambda n: [n, n],
64
+ 'valuesBenchmark': [5],
65
+ 'valuesTestParallelization': [*range(2, 4)],
66
+ # offset 1: hypothetically, if I were to load the offset from OEIS, I could use it to
67
+ # determine if a sequence is defined at n=0, which would affect, for example, the valuesTestValidation.
68
+ 'valuesTestValidation': [1, random.randint(2, 4)],
69
+ },
70
+ }
71
+
72
+ oeisIDsImplemented: Final[List[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
73
+ """Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
74
+
75
+ def _validateOEISid(oeisIDcandidate: str) -> str:
76
+ """
77
+ Validates an OEIS sequence ID against implemented sequences.
78
+
79
+ If the provided ID is recognized within the application's implemented
80
+ OEIS sequences, the function returns the verified ID in uppercase.
81
+ Otherwise, a KeyError is raised indicating that the sequence is not
82
+ directly supported.
83
+
84
+ Parameters:
85
+ oeisIDcandidate: The OEIS sequence identifier to validate.
86
+
87
+ Returns:
88
+ oeisID: The validated and possibly modified OEIS sequence ID, if recognized.
89
+
90
+ Raises:
91
+ KeyError: If the provided sequence ID is not directly implemented.
92
+ """
93
+ if oeisIDcandidate in oeisIDsImplemented:
94
+ return oeisIDcandidate
95
+ else:
96
+ oeisIDcleaned = str(oeisIDcandidate).upper().strip()
97
+ if oeisIDcleaned in oeisIDsImplemented:
98
+ return oeisIDcleaned
99
+ else:
100
+ raise KeyError(
101
+ f"OEIS ID {oeisIDcandidate} is not directly implemented.\n"
102
+ f"Available sequences:\n{_formatOEISsequenceInfo()}"
103
+ )
104
+
105
+ def _getFilenameOEISbFile(oeisID: str) -> str:
106
+ oeisID = _validateOEISid(oeisID)
107
+ return f"b{oeisID[1:]}.txt"
108
+
109
+ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
110
+ """
111
+ Parses the content of an OEIS b-file for a given sequence ID.
112
+ This function processes a multiline string representing an OEIS b-file and
113
+ creates a dictionary mapping integer indices to their corresponding sequence
114
+ values. The first line of the b-file is expected to contain a comment that
115
+ matches the given sequence ID. If it does not match, a ValueError is raised.
116
+
117
+ Parameters:
118
+ OEISbFile: A multiline string representing an OEIS b-file.
119
+ oeisID: The expected OEIS sequence identifier.
120
+ Returns:
121
+ OEISsequence: A dictionary where each key is an integer index `n` and
122
+ each value is the sequence value `a(n)` corresponding to that index.
123
+ Raises:
124
+ ValueError: If the first line of the file does not indicate the expected
125
+ sequence ID or if the content format is invalid.
126
+ """
127
+ bFileLines = OEISbFile.strip().splitlines()
128
+ # The first line has the sequence ID
129
+ if not bFileLines.pop(0).startswith(f"# {oeisID}"):
130
+ raise ValueError(f"Content does not match sequence {oeisID}")
131
+
132
+ OEISsequence = {}
133
+ for line in bFileLines:
134
+ if line.startswith('#'):
135
+ continue
136
+ n, aOFn = map(int, line.split())
137
+ OEISsequence[n] = aOFn
138
+ return OEISsequence
139
+
140
+ try:
141
+ _pathCache = pathlib.Path(__file__).parent / ".cache"
142
+ except NameError:
143
+ _pathCache = pathlib.Path.home() / ".mapFoldingCache"
144
+
145
+ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
146
+ """
147
+ Retrieves the specified OEIS sequence as a dictionary mapping integer indices
148
+ to their corresponding values.
149
+ This function checks for a cached local copy of the sequence data, using it if
150
+ it has not expired. Otherwise, it fetches the sequence data from the OEIS
151
+ website and writes it to the cache. The parsed data is returned as a dictionary
152
+ mapping each index to its sequence value.
153
+
154
+ Parameters:
155
+ oeisID: The identifier of the OEIS sequence to retrieve.
156
+ Returns:
157
+ OEISsequence: A dictionary where each key is an integer index, `n`, and each
158
+ value is the corresponding "a(n)" from the OEIS entry.
159
+ Raises:
160
+ ValueError: If the cached or downloaded file format is invalid.
161
+ IOError: If there is an error reading from or writing to the local cache.
162
+ """
163
+
164
+ pathFilenameCache = _pathCache / _getFilenameOEISbFile(oeisID)
165
+ cacheDays = 7
166
+
167
+ tryCache = False
168
+ if pathFilenameCache.exists():
169
+ fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
170
+ tryCache = fileAge < timedelta(days=cacheDays)
171
+
172
+ if tryCache:
173
+ try:
174
+ OEISbFile = pathFilenameCache.read_text()
175
+ return _parseBFileOEIS(OEISbFile, oeisID)
176
+ except (ValueError, IOError):
177
+ tryCache = False
178
+
179
+ urlOEISbFile = f"https://oeis.org/{oeisID}/{_getFilenameOEISbFile(oeisID)}"
180
+ httpResponse: urllib.response.addinfourl = urllib.request.urlopen(urlOEISbFile)
181
+ OEISbFile = httpResponse.read().decode('utf-8')
182
+
183
+ if not tryCache:
184
+ pathFilenameCache.parent.mkdir(parents=True, exist_ok=True)
185
+ pathFilenameCache.write_text(OEISbFile)
186
+
187
+ return _parseBFileOEIS(OEISbFile, oeisID)
188
+
189
+ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
190
+ """
191
+ Creates a dictionary mapping OEIS IDs to their corresponding settings.
192
+
193
+ This function initializes settings for each implemented OEIS sequence by combining
194
+ hardcoded values with dynamically retrieved OEIS sequence values.
195
+
196
+ Returns:
197
+ Dict[str, SettingsOEIS]: A dictionary where:
198
+ - Keys are OEIS sequence IDs (str)
199
+ - Values are SettingsOEIS objects containing:
200
+ - description: Text description of the sequence
201
+ - getMapShape: Function to get dimensions
202
+ - valuesBenchmark: Benchmark values
203
+ - valuesKnown: Known values from OEIS
204
+ - valuesTestValidation: Values for test validation
205
+ - valueUnknown: First unknown value in sequence
206
+
207
+ Note:
208
+ Relies on global variables:
209
+ - oeisIDsImplemented: List of implemented OEIS sequence IDs
210
+ - settingsOEIShardcodedValues: Dictionary of hardcoded settings per sequence
211
+ """
212
+ settingsTarget = {}
213
+ for oeisID in oeisIDsImplemented:
214
+ valuesKnownSherpa = _getOEISidValues(oeisID)
215
+ settingsTarget[oeisID] = SettingsOEIS(
216
+ description=settingsOEIShardcodedValues[oeisID]['description'],
217
+ getMapShape=settingsOEIShardcodedValues[oeisID]['getMapShape'],
218
+ valuesBenchmark=settingsOEIShardcodedValues[oeisID]['valuesBenchmark'],
219
+ valuesTestParallelization=settingsOEIShardcodedValues[oeisID]['valuesTestParallelization'],
220
+ valuesTestValidation=settingsOEIShardcodedValues[oeisID]['valuesTestValidation'],
221
+ valuesKnown = valuesKnownSherpa,
222
+ valueUnknown = max(valuesKnownSherpa.keys(), default=0) + 1
223
+ )
224
+ return settingsTarget
225
+
226
+ settingsOEIS: Dict[str, SettingsOEIS] = makeSettingsOEIS()
227
+ """All values and settings for `oeisIDsImplemented`."""
228
+
229
+ """
230
+ Section: private functions"""
231
+
232
+ def _formatHelpText() -> str:
233
+ """Format standardized help text for both CLI and interactive use."""
234
+ exampleOEISid = oeisIDsImplemented[0]
235
+ exampleN = settingsOEIS[exampleOEISid]['valuesTestValidation'][-1]
236
+
237
+ return (
238
+ "\nAvailable OEIS sequences:\n"
239
+ f"{_formatOEISsequenceInfo()}\n"
240
+ "\nUsage examples:\n"
241
+ " Command line:\n"
242
+ f" OEIS_for_n {exampleOEISid} {exampleN}\n"
243
+ " Python:\n"
244
+ " from mapFolding import oeisIDfor_n\n"
245
+ f" foldsTotal = oeisIDfor_n('{exampleOEISid}', {exampleN})"
246
+ )
247
+
248
+ def _formatOEISsequenceInfo() -> str:
249
+ """Format information about available OEIS sequences for display or error messages."""
250
+ return "\n".join(
251
+ f" {oeisID}: {settingsOEIS[oeisID]['description']}"
252
+ for oeisID in oeisIDsImplemented
253
+ )
254
+
255
+ """
256
+ Section: public functions"""
257
+
258
+ def oeisIDfor_n(oeisID: str, n: int) -> int:
259
+ """
260
+ Calculate a(n) of a sequence from "The On-Line Encyclopedia of Integer Sequences" (OEIS).
261
+
262
+ Parameters:
263
+ oeisID: The ID of the OEIS sequence.
264
+ n: A non-negative integer for which to calculate the sequence value.
265
+
266
+ Returns:
267
+ sequenceValue: a(n) of the OEIS sequence.
268
+
269
+ Raises:
270
+ ValueError: If n is negative.
271
+ KeyError: If the OEIS sequence ID is not directly implemented.
272
+ """
273
+ oeisID = _validateOEISid(oeisID)
274
+
275
+ if not isinstance(n, int) or n < 0:
276
+ raise ValueError("`n` must be non-negative integer.")
277
+
278
+ listDimensions = settingsOEIS[oeisID]['getMapShape'](n)
279
+
280
+ if n <= 1 or len(listDimensions) < 2:
281
+ foldsTotal = settingsOEIS[oeisID]['valuesKnown'].get(n, None)
282
+ if foldsTotal is not None:
283
+ return foldsTotal
284
+ else:
285
+ raise ArithmeticError(f"OEIS sequence {oeisID} is not defined at n={n}.")
286
+
287
+ return countFolds(listDimensions)
288
+
289
+ def OEIS_for_n() -> None:
290
+ """Command-line interface for oeisIDfor_n."""
291
+ parserCLI = argparse.ArgumentParser(
292
+ description="Calculate a(n) for an OEIS sequence.",
293
+ epilog=_formatHelpText(),
294
+ formatter_class=argparse.RawDescriptionHelpFormatter
295
+ )
296
+ parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
297
+ parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
298
+
299
+ argumentsCLI = parserCLI.parse_args()
300
+
301
+ timeStart = time.perf_counter()
302
+
303
+ try:
304
+ print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
305
+ except (KeyError, ValueError, ArithmeticError) as ERRORmessage:
306
+ print(f"Error: {ERRORmessage}", file=sys.stderr)
307
+ sys.exit(1)
308
+
309
+ timeElapsed = time.perf_counter() - timeStart
310
+ print(f"Time elapsed: {timeElapsed:.3f} seconds")
311
+
312
+ def clearOEIScache() -> None:
313
+ """Delete all cached OEIS sequence files."""
314
+ if not _pathCache.exists():
315
+ print(f"Cache directory, {_pathCache}, not found - nothing to clear.")
316
+ return
317
+ else:
318
+ for oeisID in settingsOEIS:
319
+ pathFilenameCache = _pathCache / _getFilenameOEISbFile(oeisID)
320
+ pathFilenameCache.unlink(missing_ok=True)
321
+
322
+ print(f"Cache cleared from {_pathCache}")
323
+
324
+ def getOEISids() -> None:
325
+ """Print all available OEIS sequence IDs that are directly implemented."""
326
+ print(_formatHelpText())
327
+
328
+ if __name__ == "__main__":
329
+ getOEISids()
@@ -0,0 +1,62 @@
1
+ import importlib.util
2
+ from mapFolding import getPathFilenameFoldsTotal, computationState, outfitCountFolds, getAlgorithmSource
3
+ from typing import Any, Literal, Optional, Sequence, Type, overload
4
+ import pathlib
5
+ import pickle
6
+
7
+ @overload
8
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[True]
9
+ , **keywordArguments: Optional[str]) -> pathlib.Path:
10
+ ...
11
+
12
+ @overload
13
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: Literal[False]
14
+ , **keywordArguments: Optional[str]) -> computationState:
15
+ ...
16
+
17
+ def makeStateJob(listDimensions: Sequence[int], *, writeJob: bool = True, **keywordArguments: Optional[str]) -> computationState | pathlib.Path:
18
+ """
19
+ Creates a computation state job for map folding calculations and optionally saves it to disk.
20
+
21
+ This function initializes a computation state for map folding calculations based on the given dimensions,
22
+ sets up the initial counting configuration, and can optionally save the state to a pickle file.
23
+
24
+ Parameters
25
+ ----------
26
+ listDimensions : Sequence[int]
27
+ The dimensions of the map to be folded, typically as [height, width].
28
+ writeJob : bool, optional
29
+ If True, saves the computation state to disk. If False, returns the state object directly.
30
+ Default is True.
31
+ **keywordArguments : Optional[str]
32
+ Additional keyword arguments to be passed to the outfitCountFolds function.
33
+
34
+ Returns
35
+ -------
36
+ Union[computationState, pathlib.Path]
37
+ If writeJob is False, returns the computation state object.
38
+ If writeJob is True, returns the Path object pointing to the saved state file.
39
+
40
+ Notes
41
+ -----
42
+ The function creates necessary directories and saves the state as a pickle file
43
+ when writeJob is True. The file is saved in a directory structure based on the map shape.
44
+ """
45
+
46
+ stateUniversal: computationState = outfitCountFolds(listDimensions, computationDivisions=None, CPUlimit=None, **keywordArguments)
47
+
48
+
49
+ moduleSource = getAlgorithmSource()
50
+ moduleSource.countInitialize(stateUniversal['connectionGraph'], stateUniversal['gapsWhere'], stateUniversal['my'], stateUniversal['track'])
51
+
52
+ if not writeJob:
53
+ return stateUniversal
54
+
55
+ pathFilenameChopChop = getPathFilenameFoldsTotal(stateUniversal['mapShape'])
56
+ suffix = pathFilenameChopChop.suffix
57
+ pathJob = pathlib.Path(str(pathFilenameChopChop)[0:-len(suffix)])
58
+ pathJob.mkdir(parents=True, exist_ok=True)
59
+ pathFilenameJob = pathJob / 'stateJob.pkl'
60
+
61
+ pathFilenameJob.write_bytes(pickle.dumps(stateUniversal))
62
+ return pathFilenameJob
@@ -0,0 +1,29 @@
1
+ from mapFolding import getAlgorithmSource, relativePathSyntheticModules
2
+ from mapFolding import setDatatypeModule, setDatatypeFoldsTotal, setDatatypeElephino, setDatatypeLeavesTotal
3
+ from typing import Optional
4
+ import ast
5
+ import inspect
6
+ import pathlib
7
+ import sys
8
+
9
+ def transformPythonToJAX(codePython: str):
10
+ astPython = ast.parse(codePython)
11
+
12
+ def writeJax(*, codeSource: Optional[str] = None, pathFilenameAlgorithm: Optional[pathlib.Path] = None, pathFilenameDestination: Optional[pathlib.Path] = None) -> None:
13
+ if codeSource is None and pathFilenameAlgorithm is None:
14
+ algorithmSource = getAlgorithmSource()
15
+ codeSource = inspect.getsource(algorithmSource)
16
+ transformedText = transformPythonToJAX(codeSource)
17
+ pathFilenameAlgorithm = pathlib.Path(inspect.getfile(algorithmSource))
18
+ else:
19
+ raise NotImplementedError("You haven't written this part yet.")
20
+ if pathFilenameDestination is None:
21
+ pathFilenameDestination = pathFilenameAlgorithm.parent / relativePathSyntheticModules / "countJax.py"
22
+ # pathFilenameDestination.write_text(transformedText)
23
+
24
+ if __name__ == '__main__':
25
+ setDatatypeModule('jax.numpy', sourGrapes=True)
26
+ setDatatypeFoldsTotal('int64', sourGrapes=True)
27
+ setDatatypeElephino('uint8', sourGrapes=True)
28
+ setDatatypeLeavesTotal('uint8', sourGrapes=True)
29
+ writeJax()
@@ -1,5 +1,5 @@
1
1
  from mapFolding import getPathFilenameFoldsTotal, indexMy, indexTrack
2
- from mapFolding import make_dtype, datatypeLargeDEFAULT, datatypeMediumDEFAULT, datatypeSmallDEFAULT, datatypeModuleDEFAULT
2
+ from mapFolding import setDatatypeElephino, setDatatypeFoldsTotal, setDatatypeLeavesTotal, setDatatypeModule, hackSSOTdatatype
3
3
  from someAssemblyRequired import makeStateJob
4
4
  from typing import Optional
5
5
  import importlib
@@ -13,9 +13,36 @@ import python_minifier
13
13
  identifierCallableLaunch = "goGoGadgetAbsurdity"
14
14
 
15
15
  def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: str) -> str:
16
- def process_nested_array(arraySlice):
16
+ """Converts a NumPy array into a compressed string representation using run-length encoding (RLE).
17
+
18
+ This function takes a NumPy array and converts it into an optimized string representation by:
19
+ 1. Compressing consecutive sequences of numbers into range objects
20
+ 2. Minimizing repeated zeros using array multiplication syntax
21
+ 3. Converting the result into a valid Python array initialization statement
22
+
23
+ Parameters:
24
+ arrayTarget (numpy.ndarray): The input NumPy array to be converted
25
+ identifierName (str): The variable name to use in the output string
26
+
27
+ Returns:
28
+ str: A string containing Python code that recreates the input array in compressed form.
29
+ Format: "{identifierName} = numpy.array({compressed_data}, dtype=numpy.{dtype})"
30
+
31
+ Example:
32
+ >>> arr = numpy.array([[0,0,0,1,2,3,4,0,0]])
33
+ >>> print(makeStrRLEcompacted(arr, "myArray"))
34
+ "myArray = numpy.array([[0]*3,*range(1,5),[0]*2], dtype=numpy.int64)"
35
+
36
+ Notes:
37
+ - Sequences of 4 or fewer numbers are kept as individual values
38
+ - Sequences longer than 4 numbers are converted to range objects
39
+ - Consecutive zeros are compressed using multiplication syntax
40
+ - The function preserves the original array's dtype
41
+ """
42
+
43
+ def compressRangesNDArrayNoFlatten(arraySlice):
17
44
  if isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim > 1:
18
- return [process_nested_array(arraySlice[index]) for index in range(arraySlice.shape[0])]
45
+ return [compressRangesNDArrayNoFlatten(arraySlice[index]) for index in range(arraySlice.shape[0])]
19
46
  elif isinstance(arraySlice, numpy.ndarray) and arraySlice.ndim == 1:
20
47
  listWithRanges = []
21
48
  for group in more_itertools.consecutive_groups(arraySlice.tolist()):
@@ -28,7 +55,7 @@ def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: str) -> str:
28
55
  return listWithRanges
29
56
  return arraySlice
30
57
 
31
- arrayAsNestedLists = process_nested_array(arrayTarget)
58
+ arrayAsNestedLists = compressRangesNDArrayNoFlatten(arrayTarget)
32
59
 
33
60
  stringMinimized = python_minifier.minify(str(arrayAsNestedLists))
34
61
  commaZeroMaximum = arrayTarget.shape[-1] - 1
@@ -40,25 +67,47 @@ def makeStrRLEcompacted(arrayTarget: numpy.ndarray, identifierName: str) -> str:
40
67
 
41
68
  return f"{identifierName} = numpy.array({stringMinimized}, dtype=numpy.{arrayTarget.dtype})"
42
69
 
43
- def writeModuleWithNumba(listDimensions, **keywordArguments: Optional[str]) -> pathlib.Path:
44
- datatypeLarge = keywordArguments.get('datatypeLarge', datatypeLargeDEFAULT)
45
- datatypeMedium = keywordArguments.get('datatypeMedium', datatypeMediumDEFAULT)
46
- datatypeSmall = keywordArguments.get('datatypeSmall', datatypeSmallDEFAULT)
47
- datatypeModule = keywordArguments.get('datatypeModule', datatypeModuleDEFAULT)
48
-
49
- dtypeLarge = make_dtype(datatypeLarge, datatypeModule) # type: ignore
50
- dtypeMedium = make_dtype(datatypeMedium, datatypeModule) # type: ignore
51
- dtypeSmall = make_dtype(datatypeSmall, datatypeModule) # type: ignore
52
-
53
- stateJob = makeStateJob(listDimensions, writeJob=False, dtypeLarge = dtypeLarge, dtypeMedium = dtypeMedium, dtypeSmall = dtypeSmall)
70
+ def writeModuleWithNumba(listDimensions) -> pathlib.Path:
71
+ """
72
+ Writes a Numba-optimized Python module for map folding calculations.
73
+
74
+ This function takes map dimensions and generates a specialized Python module with Numba
75
+ optimizations. It processes a sequential counting algorithm, adds Numba decorators and
76
+ necessary data structures, and writes the resulting code to a file.
77
+
78
+ Parameters:
79
+ listDimensions: List of integers representing the dimensions of the map to be folded.
80
+
81
+ Returns:
82
+ pathlib.Path: Path to the generated Python module file.
83
+
84
+ The generated module includes:
85
+ - Numba JIT compilation decorators for performance optimization
86
+ - Required numpy and numba imports
87
+ - Dynamic and static data structures needed for folding calculations
88
+ - Processed algorithm from the original sequential counter
89
+ - Launch code for standalone execution
90
+ - Code to write the final fold count to a file
91
+ The function handles:
92
+ - Translation of original code to Numba-compatible syntax
93
+ - Insertion of pre-calculated values from the state job
94
+ - Management of variable declarations and assignments
95
+ - Setup of proper data types for Numba optimization
96
+ - Organization of the output file structure
97
+
98
+ Note:
99
+ The generated module requires Numba and numpy to be installed.
100
+ The output file will be placed in the same directory as the folds total file,
101
+ with a .py extension.
102
+ """
103
+ stateJob = makeStateJob(listDimensions, writeJob=False)
54
104
  pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateJob['mapShape'])
55
105
 
56
106
  from syntheticModules import countSequential
57
107
  algorithmSource = countSequential
58
108
  codeSource = inspect.getsource(algorithmSource)
59
109
 
60
- if datatypeLarge:
61
- lineNumba = f"@numba.jit(numba.types.{datatypeLarge}(), cache=True, nopython=True, fastmath=True, forceinline=True, inline='always', looplift=False, _nrt=True, error_model='numpy', parallel=False, boundscheck=False, no_cfunc_wrapper=True, no_cpython_wrapper=False)"
110
+ lineNumba = f"@numba.jit(numba.types.{hackSSOTdatatype('datatypeFoldsTotal')}(), cache=True, nopython=True, fastmath=True, forceinline=True, inline='always', looplift=False, _nrt=True, error_model='numpy', parallel=False, boundscheck=False, no_cfunc_wrapper=False, no_cpython_wrapper=False)"
62
111
 
63
112
  linesImport = "\n".join([
64
113
  "import numpy"
@@ -68,8 +117,6 @@ def writeModuleWithNumba(listDimensions, **keywordArguments: Optional[str]) -> p
68
117
  ImaIndent = ' '
69
118
  linesDataDynamic = """"""
70
119
  linesDataDynamic = "\n".join([linesDataDynamic
71
- # , ImaIndent + f"foldsTotal = numba.types.{datatypeLarge}(0)"
72
- # , ImaIndent + makeStrRLEcompacted(stateJob['foldGroups'], 'foldGroups')
73
120
  , ImaIndent + makeStrRLEcompacted(stateJob['gapsWhere'], 'gapsWhere')
74
121
  ])
75
122
 
@@ -97,11 +144,18 @@ def writeModuleWithNumba(listDimensions, **keywordArguments: Optional[str]) -> p
97
144
  elif 'my[indexMy.' in lineSource:
98
145
  if 'dimensionsTotal' in lineSource:
99
146
  continue
100
- # leaf1ndex = my[indexMy.leaf1ndex.value]
147
+ # Statements are in the form: leaf1ndex = my[indexMy.leaf1ndex.value]
101
148
  identifier, statement = lineSource.split('=')
102
- lineSource = ImaIndent + identifier.strip() + f"=numba.types.{datatypeSmall}({str(eval(statement.strip()))})"
149
+ lineSource = ImaIndent + identifier.strip() + f"=numba.types.{hackSSOTdatatype(identifier.strip())}({str(eval(statement.strip()))})"
150
+ elif ': int =' in lineSource or ':int=' in lineSource:
151
+ if 'dimensionsTotal' in lineSource:
152
+ continue
153
+ # Statements are in the form: groupsOfFolds: int = 0
154
+ assignment, statement = lineSource.split('=')
155
+ identifier = assignment.split(':')[0].strip()
156
+ lineSource = ImaIndent + identifier.strip() + f"=numba.types.{hackSSOTdatatype(identifier.strip())}({str(eval(statement.strip()))})"
103
157
  elif 'track[indexTrack.' in lineSource:
104
- # leafAbove = track[indexTrack.leafAbove.value]
158
+ # Statements are in the form: leafAbove = track[indexTrack.leafAbove.value]
105
159
  identifier, statement = lineSource.split('=')
106
160
  lineSource = ImaIndent + makeStrRLEcompacted(eval(statement.strip()), identifier.strip())
107
161
  elif 'foldGroups[-1]' in lineSource:
@@ -144,11 +198,11 @@ if __name__ == '__main__':
144
198
  return pathFilenameDestination
145
199
 
146
200
  if __name__ == '__main__':
147
- listDimensions = [6,6]
148
- datatypeLarge = 'int64'
149
- datatypeMedium = 'uint8'
150
- datatypeSmall = datatypeMedium
151
- pathFilenameModule = writeModuleWithNumba(listDimensions, datatypeLarge=datatypeLarge, datatypeMedium=datatypeMedium, datatypeSmall=datatypeSmall)
201
+ listDimensions = [5,5]
202
+ setDatatypeFoldsTotal('int64', sourGrapes=True)
203
+ setDatatypeElephino('uint8', sourGrapes=True)
204
+ setDatatypeLeavesTotal('int8', sourGrapes=True)
205
+ pathFilenameModule = writeModuleWithNumba(listDimensions)
152
206
 
153
207
  # Induce numba.jit compilation
154
208
  moduleSpec = importlib.util.spec_from_file_location(pathFilenameModule.stem, pathFilenameModule)