mapFolding 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapFolding/JAX/lunnanJAX.py +206 -0
- mapFolding/JAX/taskJAX.py +313 -0
- mapFolding/__init__.py +21 -0
- mapFolding/babbage.py +12 -0
- mapFolding/beDRY.py +219 -0
- mapFolding/benchmarks/benchmarking.py +66 -0
- mapFolding/benchmarks/test_benchmarks.py +74 -0
- mapFolding/importPackages.py +5 -0
- mapFolding/lovelace.py +121 -0
- mapFolding/oeis.py +299 -0
- mapFolding/reference/hunterNumba.py +132 -0
- mapFolding/reference/irvineJavaPort.py +120 -0
- mapFolding/reference/lunnan.py +153 -0
- mapFolding/reference/lunnanNumpy.py +123 -0
- mapFolding/reference/lunnanWhile.py +121 -0
- mapFolding/reference/rotatedEntryPoint.py +240 -0
- mapFolding/startHere.py +54 -0
- mapFolding/theSSOT.py +62 -0
- mapFolding-0.2.0.dist-info/METADATA +170 -0
- mapFolding-0.2.0.dist-info/RECORD +28 -0
- mapFolding-0.2.0.dist-info/WHEEL +5 -0
- mapFolding-0.2.0.dist-info/entry_points.txt +4 -0
- mapFolding-0.2.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/conftest.py +262 -0
- tests/test_oeis.py +195 -0
- tests/test_other.py +71 -0
- tests/test_tasks.py +18 -0
mapFolding/beDRY.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""A relatively stable API for oft-needed functionality."""
|
|
2
|
+
from mapFolding.importPackages import intInnit, defineConcurrencyLimit, oopsieKwargsie
|
|
3
|
+
from mapFolding import indexMy, indexThe, indexTrack, computationState
|
|
4
|
+
from typing import Any, List, Optional, Sequence, Type, Union
|
|
5
|
+
import numpy
|
|
6
|
+
import numba
|
|
7
|
+
import numba.extending
|
|
8
|
+
import numpy.typing
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
12
|
+
"""
|
|
13
|
+
Calculate the product of non-zero, non-negative integers in the given list.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
listDimensions: A list of integers representing dimensions.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
productDimensions: The product of all positive integer dimensions. Returns 0 if all dimensions are 0.
|
|
20
|
+
"""
|
|
21
|
+
listNonNegative = parseDimensions(listDimensions, 'listDimensions')
|
|
22
|
+
listPositive = [dimension for dimension in listNonNegative if dimension > 0]
|
|
23
|
+
|
|
24
|
+
if not listPositive:
|
|
25
|
+
return 0
|
|
26
|
+
else:
|
|
27
|
+
productDimensions = 1
|
|
28
|
+
for dimension in listPositive:
|
|
29
|
+
if dimension > sys.maxsize // productDimensions:
|
|
30
|
+
raise OverflowError("Product would exceed maximum integer size")
|
|
31
|
+
productDimensions *= dimension
|
|
32
|
+
|
|
33
|
+
return productDimensions
|
|
34
|
+
|
|
35
|
+
def getTaskDivisions(CPUlimit, computationDivisions: Optional[Union[int, str]], concurrencyLimit: int, listDimensions, the: numpy.typing.NDArray[numpy.integer[Any]], ):
|
|
36
|
+
# TODO remove after restructuring the tests
|
|
37
|
+
if isinstance(computationDivisions, bool) and computationDivisions:
|
|
38
|
+
computationDivisions = "maximum"
|
|
39
|
+
|
|
40
|
+
if not computationDivisions:
|
|
41
|
+
# Coding it this way should cover `None`, `False`, and `0`.
|
|
42
|
+
the[indexThe.taskDivisions] = 0
|
|
43
|
+
elif isinstance(computationDivisions, int):
|
|
44
|
+
the[indexThe.taskDivisions] = computationDivisions
|
|
45
|
+
elif isinstance(computationDivisions, str):
|
|
46
|
+
computationDivisions = computationDivisions.lower()
|
|
47
|
+
if computationDivisions == "maximum":
|
|
48
|
+
the[indexThe.taskDivisions] = the[indexThe.leavesTotal]
|
|
49
|
+
elif computationDivisions == "cpu":
|
|
50
|
+
the[indexThe.taskDivisions] = min(concurrencyLimit, the[indexThe.leavesTotal])
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(f"I received {computationDivisions} for the parameter, `computationDivisions`, but the so-called programmer didn't implement code for that.")
|
|
53
|
+
|
|
54
|
+
if the[indexThe.taskDivisions] > the[indexThe.leavesTotal]:
|
|
55
|
+
raise ValueError(f"Problem: `taskDivisions`, ({the[indexThe.taskDivisions]}), is greater than `leavesTotal`, ({the[indexThe.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.")
|
|
56
|
+
|
|
57
|
+
return the
|
|
58
|
+
|
|
59
|
+
def makeConnectionGraph(listDimensions: Sequence[int], dtype: Optional[Type] = numpy.int64) -> numpy.typing.NDArray[numpy.integer[Any]]:
|
|
60
|
+
"""
|
|
61
|
+
Constructs a connection graph for a given list of dimensions.
|
|
62
|
+
This function generates a multi-dimensional connection graph based on the provided list of dimensions.
|
|
63
|
+
The graph represents the connections between leaves in a Cartesian product decomposition or dimensional product mapping.
|
|
64
|
+
|
|
65
|
+
Parameters:
|
|
66
|
+
listDimensions: A validated sequence of integers representing the dimensions of the map.
|
|
67
|
+
Returns:
|
|
68
|
+
connectionGraph: A 3D numpy array with shape of (dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1).
|
|
69
|
+
"""
|
|
70
|
+
leavesTotal = getLeavesTotal(listDimensions)
|
|
71
|
+
arrayDimensions = numpy.array(listDimensions, dtype=dtype)
|
|
72
|
+
dimensionsTotal = len(arrayDimensions)
|
|
73
|
+
|
|
74
|
+
# Step 1: find the cumulative product of the map's dimensions
|
|
75
|
+
cumulativeProduct = numpy.ones(dimensionsTotal + 1, dtype=dtype)
|
|
76
|
+
for index in range(1, dimensionsTotal + 1):
|
|
77
|
+
cumulativeProduct[index] = cumulativeProduct[index - 1] * arrayDimensions[index - 1]
|
|
78
|
+
|
|
79
|
+
# Step 2: create a coordinate system
|
|
80
|
+
coordinateSystem = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1), dtype=dtype)
|
|
81
|
+
|
|
82
|
+
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
83
|
+
for leaf1ndex in range(1, leavesTotal + 1):
|
|
84
|
+
coordinateSystem[dimension1ndex, leaf1ndex] = (
|
|
85
|
+
((leaf1ndex - 1) // cumulativeProduct[dimension1ndex - 1]) %
|
|
86
|
+
arrayDimensions[dimension1ndex - 1] + 1
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Step 3: create and fill the connection graph
|
|
90
|
+
connectionGraph = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1), dtype=dtype)
|
|
91
|
+
|
|
92
|
+
for dimension1ndex in range(1, dimensionsTotal + 1):
|
|
93
|
+
for activeLeaf1ndex in range(1, leavesTotal + 1):
|
|
94
|
+
for connectee1ndex in range(1, activeLeaf1ndex + 1):
|
|
95
|
+
# Base coordinate conditions
|
|
96
|
+
isFirstCoord = coordinateSystem[dimension1ndex, connectee1ndex] == 1
|
|
97
|
+
isLastCoord = coordinateSystem[dimension1ndex, connectee1ndex] == arrayDimensions[dimension1ndex - 1]
|
|
98
|
+
exceedsActive = connectee1ndex + cumulativeProduct[dimension1ndex - 1] > activeLeaf1ndex
|
|
99
|
+
|
|
100
|
+
# Parity check
|
|
101
|
+
isEvenParity = (coordinateSystem[dimension1ndex, activeLeaf1ndex] & 1) == \
|
|
102
|
+
(coordinateSystem[dimension1ndex, connectee1ndex] & 1)
|
|
103
|
+
|
|
104
|
+
# Determine connection value
|
|
105
|
+
if (isEvenParity and isFirstCoord) or (not isEvenParity and (isLastCoord or exceedsActive)):
|
|
106
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
107
|
+
elif isEvenParity and not isFirstCoord:
|
|
108
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex - cumulativeProduct[dimension1ndex - 1]
|
|
109
|
+
elif not isEvenParity and not (isLastCoord or exceedsActive):
|
|
110
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex + cumulativeProduct[dimension1ndex - 1]
|
|
111
|
+
else:
|
|
112
|
+
connectionGraph[dimension1ndex, activeLeaf1ndex, connectee1ndex] = connectee1ndex
|
|
113
|
+
|
|
114
|
+
return connectionGraph
|
|
115
|
+
|
|
116
|
+
def outfitFoldings(
|
|
117
|
+
listDimensions: Sequence[int],
|
|
118
|
+
computationDivisions: Optional[Union[int, str]] = None,
|
|
119
|
+
CPUlimit: Optional[Union[int, float, bool]] = None,
|
|
120
|
+
dtypeDefault: Optional[Type] = numpy.int64, # TODO consider allowing a type or a "signal", such as "minimum", "safe", "maximum"
|
|
121
|
+
dtypeLarge: Optional[Type] = numpy.int64, # Can/should I use numba types?
|
|
122
|
+
) -> computationState:
|
|
123
|
+
the = numpy.zeros(len(indexThe), dtype=dtypeDefault)
|
|
124
|
+
|
|
125
|
+
mapShape = tuple(sorted(validateListDimensions(listDimensions)))
|
|
126
|
+
the[indexThe.leavesTotal] = getLeavesTotal(mapShape)
|
|
127
|
+
the[indexThe.dimensionsTotal] = len(mapShape)
|
|
128
|
+
concurrencyLimit = setCPUlimit(CPUlimit)
|
|
129
|
+
|
|
130
|
+
the = getTaskDivisions(CPUlimit, computationDivisions, concurrencyLimit, listDimensions, the)
|
|
131
|
+
|
|
132
|
+
stateInitialized = computationState(
|
|
133
|
+
connectionGraph = makeConnectionGraph(mapShape, dtype=dtypeDefault),
|
|
134
|
+
foldsTotal = numpy.zeros(the[indexThe.leavesTotal], dtype=numpy.int64),
|
|
135
|
+
mapShape = mapShape,
|
|
136
|
+
my = numpy.zeros(len(indexMy), dtype=dtypeLarge),
|
|
137
|
+
gapsWhere = numpy.zeros(int(the[indexThe.leavesTotal]) * int(the[indexThe.leavesTotal]) + 1, dtype=dtypeDefault),
|
|
138
|
+
the = the,
|
|
139
|
+
track = numpy.zeros((len(indexTrack), the[indexThe.leavesTotal] + 1), dtype=dtypeLarge)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
stateInitialized['my'][indexMy.leaf1ndex.value] = 1
|
|
143
|
+
|
|
144
|
+
return stateInitialized
|
|
145
|
+
|
|
146
|
+
def parseDimensions(dimensions: Sequence[int], parameterName: str = 'unnamed parameter') -> List[int]:
|
|
147
|
+
"""
|
|
148
|
+
Parse and validate a list of dimensions.
|
|
149
|
+
|
|
150
|
+
Parameters:
|
|
151
|
+
listDimensions: List of integers representing dimensions
|
|
152
|
+
parameterName ('unnamed parameter'): Name of the parameter for error messages. Defaults to 'unnamed parameter'
|
|
153
|
+
Returns:
|
|
154
|
+
listNonNegative: List of validated non-negative integers
|
|
155
|
+
Raises:
|
|
156
|
+
ValueError: If any dimension is negative or if the list is empty
|
|
157
|
+
TypeError: If any element cannot be converted to integer (raised by intInnit)
|
|
158
|
+
"""
|
|
159
|
+
listValidated = intInnit(dimensions, parameterName)
|
|
160
|
+
listNonNegative = []
|
|
161
|
+
for dimension in listValidated:
|
|
162
|
+
if dimension < 0:
|
|
163
|
+
raise ValueError(f"Dimension {dimension} must be non-negative")
|
|
164
|
+
listNonNegative.append(dimension)
|
|
165
|
+
|
|
166
|
+
if not listNonNegative:
|
|
167
|
+
raise ValueError("At least one dimension must be non-negative")
|
|
168
|
+
|
|
169
|
+
return listNonNegative
|
|
170
|
+
|
|
171
|
+
def setCPUlimit(CPUlimit: Union[int, float, bool, None]):
|
|
172
|
+
"""Sets CPU limit for concurrent operations using Numba.
|
|
173
|
+
This function configures the number of CPU threads that Numba can use for parallel execution.
|
|
174
|
+
Note that this setting only affects Numba-jitted functions that have not yet been imported.
|
|
175
|
+
Parameters:
|
|
176
|
+
CPUlimit (Union[int, float, bool, None]): The CPU limit to set.
|
|
177
|
+
- If int/float: Specifies number of CPU threads to use
|
|
178
|
+
- If bool: True uses all available CPUs, False uses 1 CPU
|
|
179
|
+
- If None: Uses system default
|
|
180
|
+
Returns:
|
|
181
|
+
concurrencyLimit: The actual concurrency limit that was set
|
|
182
|
+
Raises:
|
|
183
|
+
TypeError: If CPUlimit is not of the expected types
|
|
184
|
+
"""
|
|
185
|
+
if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
186
|
+
CPUlimit = oopsieKwargsie(CPUlimit)
|
|
187
|
+
|
|
188
|
+
concurrencyLimit = defineConcurrencyLimit(CPUlimit)
|
|
189
|
+
# NOTE `set_num_threads` only affects "jitted" functions that have _not_ yet been "imported"
|
|
190
|
+
numba.set_num_threads(concurrencyLimit)
|
|
191
|
+
|
|
192
|
+
return concurrencyLimit
|
|
193
|
+
|
|
194
|
+
def validateListDimensions(listDimensions: Sequence[int]) -> List[int]:
|
|
195
|
+
"""
|
|
196
|
+
Validates and processes a list of dimensions.
|
|
197
|
+
|
|
198
|
+
This function ensures that the input list of dimensions is not None,
|
|
199
|
+
parses it to ensure all dimensions are non-negative, and then filters
|
|
200
|
+
out any dimensions that are not greater than zero. If the resulting
|
|
201
|
+
list has fewer than two dimensions, a NotImplementedError is raised.
|
|
202
|
+
|
|
203
|
+
Parameters:
|
|
204
|
+
listDimensions: A list of integer dimensions to be validated.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
validDimensions: A list, with at least two elements, of only positive integers.
|
|
208
|
+
|
|
209
|
+
Raises:
|
|
210
|
+
ValueError: If the input listDimensions is None.
|
|
211
|
+
NotImplementedError: If the resulting list of positive dimensions has fewer than two elements.
|
|
212
|
+
"""
|
|
213
|
+
if not listDimensions:
|
|
214
|
+
raise ValueError(f"listDimensions is a required parameter.")
|
|
215
|
+
listNonNegative = parseDimensions(listDimensions, 'listDimensions')
|
|
216
|
+
validDimensions = [dimension for dimension in listNonNegative if dimension > 0]
|
|
217
|
+
if len(validDimensions) < 2:
|
|
218
|
+
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/.")
|
|
219
|
+
return validDimensions
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
from typing import Callable
|
|
3
|
+
import numpy
|
|
4
|
+
import pathlib
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
pathRecordedBenchmarks = pathlib.Path('mapFolding/benchmarks/marks')
|
|
8
|
+
pathRecordedBenchmarks.mkdir(parents=True, exist_ok=True)
|
|
9
|
+
pathFilenameRecordedBenchmarks = pathRecordedBenchmarks / "benchmarks.npy"
|
|
10
|
+
|
|
11
|
+
def recordBenchmarks():
|
|
12
|
+
"""Decorator to benchmark a function."""
|
|
13
|
+
def AzeemTheWrapper(functionTarget: Callable):
|
|
14
|
+
def djZeph(*arguments, **keywordArguments):
|
|
15
|
+
timeStart = time.perf_counter_ns()
|
|
16
|
+
returnValueTarget = functionTarget(*arguments, **keywordArguments)
|
|
17
|
+
timeElapsed = (time.perf_counter_ns() - timeStart) / 1e9
|
|
18
|
+
|
|
19
|
+
# Extract mapShape from arguments
|
|
20
|
+
mapShape = keywordArguments['mapShape']
|
|
21
|
+
# mapShape = tuple(arguments)[2]
|
|
22
|
+
# leavesTotal = tuple(arguments[3])[4]
|
|
23
|
+
|
|
24
|
+
# Store benchmark data in single file
|
|
25
|
+
benchmarkEntry = numpy.array([(timeElapsed, mapShape)], dtype=[('time', 'f8'), ('mapShape', 'O')])
|
|
26
|
+
# benchmarkEntry = numpy.array([(timeElapsed, leavesTotal)], dtype=[('time', 'f8'), ('leaves', 'O')])
|
|
27
|
+
|
|
28
|
+
if pathFilenameRecordedBenchmarks.exists():
|
|
29
|
+
arrayExisting = numpy.load(str(pathFilenameRecordedBenchmarks), allow_pickle=True)
|
|
30
|
+
arrayBenchmark = numpy.concatenate([arrayExisting, benchmarkEntry])
|
|
31
|
+
else:
|
|
32
|
+
arrayBenchmark = benchmarkEntry
|
|
33
|
+
|
|
34
|
+
numpy.save(str(pathFilenameRecordedBenchmarks), arrayBenchmark)
|
|
35
|
+
return returnValueTarget
|
|
36
|
+
|
|
37
|
+
return djZeph
|
|
38
|
+
return AzeemTheWrapper
|
|
39
|
+
|
|
40
|
+
def runBenchmarks(benchmarkIterations: int = 30) -> None:
|
|
41
|
+
"""Run benchmark iterations.
|
|
42
|
+
|
|
43
|
+
Parameters:
|
|
44
|
+
benchmarkIterations (30): Number of benchmark iterations to run
|
|
45
|
+
"""
|
|
46
|
+
# TODO warmUp (False): Whether to perform one warm-up iteration
|
|
47
|
+
|
|
48
|
+
import itertools
|
|
49
|
+
from tqdm.auto import tqdm
|
|
50
|
+
from mapFolding.oeis import settingsOEIS, oeisIDfor_n
|
|
51
|
+
from concurrent.futures import ProcessPoolExecutor, as_completed
|
|
52
|
+
max_workers = 6
|
|
53
|
+
|
|
54
|
+
listParametersOEIS = [(oeisIdentifier, dimensionValue) for oeisIdentifier, settings in settingsOEIS.items() for dimensionValue in settings['valuesBenchmark']]
|
|
55
|
+
# for (oeisIdentifier, dimensionValue), iterationIndex in tqdm(itertools.product(listParametersOEIS, range(benchmarkIterations)), total=len(listParametersOEIS) * benchmarkIterations):
|
|
56
|
+
# oeisIDfor_n(oeisIdentifier, dimensionValue)
|
|
57
|
+
listCartesianProduct = list(itertools.product(listParametersOEIS, range(benchmarkIterations)))
|
|
58
|
+
with ProcessPoolExecutor(max_workers) as concurrencyManager:
|
|
59
|
+
listConcurrency = [concurrencyManager.submit(oeisIDfor_n, *parameters[0]) for parameters in listCartesianProduct]
|
|
60
|
+
for complete in tqdm(as_completed(listConcurrency), total=len(listCartesianProduct)):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
if __name__ == '__main__':
|
|
64
|
+
multiprocessing.set_start_method('spawn')
|
|
65
|
+
pathFilenameRecordedBenchmarks.unlink(missing_ok=True)
|
|
66
|
+
runBenchmarks(30)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from ...tests.conftest import *
|
|
2
|
+
from .benchmarking import recordBenchmarks, runBenchmarks
|
|
3
|
+
import numpy
|
|
4
|
+
import pathlib
|
|
5
|
+
import pytest
|
|
6
|
+
import unittest.mock
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
def test_recordBenchmarks_decorator(pathBenchmarksTesting: pathlib.Path,
|
|
10
|
+
listDimensionsTestFunctionality: List[int],
|
|
11
|
+
mockBenchmarkTimer: unittest.mock.MagicMock):
|
|
12
|
+
"""Test that the decorator correctly records benchmark data."""
|
|
13
|
+
@recordBenchmarks()
|
|
14
|
+
def functionTest(listDimensions: List[int]) -> int:
|
|
15
|
+
return sum(listDimensions)
|
|
16
|
+
|
|
17
|
+
with mockBenchmarkTimer:
|
|
18
|
+
mockBenchmarkTimer.side_effect = [0, 1e9]
|
|
19
|
+
result = functionTest(listDimensionsTestFunctionality)
|
|
20
|
+
|
|
21
|
+
# Verify function still works normally
|
|
22
|
+
assert result == sum(listDimensionsTestFunctionality)
|
|
23
|
+
|
|
24
|
+
# Verify benchmark data was saved
|
|
25
|
+
arrayBenchmarks = numpy.load(str(pathBenchmarksTesting), allow_pickle=True)
|
|
26
|
+
assert len(arrayBenchmarks) == 1
|
|
27
|
+
assert arrayBenchmarks[0]['time'] == 1.0
|
|
28
|
+
assert tuple(arrayBenchmarks[0]['dimensions']) == tuple(listDimensionsTestFunctionality)
|
|
29
|
+
|
|
30
|
+
def test_recordBenchmarks_multiple_calls(pathBenchmarksTesting: pathlib.Path,
|
|
31
|
+
listDimensionsTestFunctionality: List[int],
|
|
32
|
+
mockBenchmarkTimer: unittest.mock.MagicMock):
|
|
33
|
+
"""Test that multiple function calls append to benchmark data."""
|
|
34
|
+
@recordBenchmarks()
|
|
35
|
+
def functionTest(listDimensions: List[int]) -> int:
|
|
36
|
+
return sum(listDimensions)
|
|
37
|
+
|
|
38
|
+
with mockBenchmarkTimer:
|
|
39
|
+
mockBenchmarkTimer.side_effect = [0, 1e9, 2e9, 4e9]
|
|
40
|
+
functionTest(listDimensionsTestFunctionality)
|
|
41
|
+
functionTest(listDimensionsTestFunctionality)
|
|
42
|
+
|
|
43
|
+
arrayBenchmarks = numpy.load(str(pathBenchmarksTesting), allow_pickle=True)
|
|
44
|
+
assert len(arrayBenchmarks) == 2
|
|
45
|
+
assert arrayBenchmarks[0]['time'] == 1.0
|
|
46
|
+
assert arrayBenchmarks[1]['time'] == 2.0
|
|
47
|
+
|
|
48
|
+
# NOTE This test tries to collect benchmark data without ensuring that a function is decorated.
|
|
49
|
+
# def test_runBenchmarks_integration(pathBenchmarksTesting: pathlib.Path, listDimensionsTestFunctionality: List[int]):
|
|
50
|
+
# """Test runBenchmarks creates valid benchmark data."""
|
|
51
|
+
# countIterations = 2
|
|
52
|
+
# runBenchmarks(countIterations)
|
|
53
|
+
|
|
54
|
+
# arrayBenchmarks = numpy.load(str(pathBenchmarksTesting), allow_pickle=True)
|
|
55
|
+
# assert len(arrayBenchmarks) > 0 # Should have recorded some benchmarks
|
|
56
|
+
|
|
57
|
+
# # Verify data structure integrity
|
|
58
|
+
# assert arrayBenchmarks.dtype.names == ('time', 'dimensions')
|
|
59
|
+
# assert all(isinstance(record['time'], float) for record in arrayBenchmarks)
|
|
60
|
+
# assert all(isinstance(record['dimensions'], tuple) for record in arrayBenchmarks)
|
|
61
|
+
|
|
62
|
+
# # Verify at least one benchmark entry matches our test dimensions
|
|
63
|
+
# assert any(tuple(listDimensionsTestFunctionality) == record['dimensions'] for record in arrayBenchmarks)
|
|
64
|
+
|
|
65
|
+
# NOTE This test tries to collect benchmark data without ensuring that a function is decorated.
|
|
66
|
+
# @pytest.mark.parametrize("countIterations", [1, 2])
|
|
67
|
+
# def test_runBenchmarks_iterations(countIterations: int, pathBenchmarksTesting: pathlib.Path, listDimensionsTestFunctionality: List[int]):
|
|
68
|
+
# """Test runBenchmarks records data for each iteration."""
|
|
69
|
+
# runBenchmarks(countIterations)
|
|
70
|
+
# arrayBenchmarks = numpy.load(str(pathBenchmarksTesting), allow_pickle=True)
|
|
71
|
+
|
|
72
|
+
# # Should have at least countIterations entries for our test dimensions
|
|
73
|
+
# countMatches = sum(1 for record in arrayBenchmarks if tuple(listDimensionsTestFunctionality) == record['dimensions'])
|
|
74
|
+
# assert countMatches >= countIterations
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"""Experiment"""
|
|
2
|
+
from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
3
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteConcurrencyLimit
|
|
4
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteIntInnit
|
|
5
|
+
from Z0Z_tools.pytest_parseParameters import makeTestSuiteOopsieKwargsie
|
mapFolding/lovelace.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from mapFolding import indexMy, indexThe, indexTrack
|
|
2
|
+
from numpy import integer
|
|
3
|
+
from numpy.typing import NDArray
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
import numba
|
|
6
|
+
import numpy
|
|
7
|
+
|
|
8
|
+
@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]]):
|
|
10
|
+
if the[indexThe.taskDivisions.value] == 0:
|
|
11
|
+
return True
|
|
12
|
+
return my[indexMy.leaf1ndex.value] != the[indexThe.taskDivisions.value] or \
|
|
13
|
+
(my[indexMy.leafConnectee.value] % the[indexThe.taskDivisions.value]) == my[indexMy.taskIndex.value]
|
|
14
|
+
|
|
15
|
+
@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:
|
|
18
|
+
return my[indexMy.dimensionsUnconstrained.value] == the[indexThe.dimensionsTotal.value]
|
|
19
|
+
else:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
@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:
|
|
25
|
+
return False
|
|
26
|
+
else:
|
|
27
|
+
if my[indexMy.gap1ndex.value] > 0:
|
|
28
|
+
return True
|
|
29
|
+
else:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
@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] ):
|
|
34
|
+
while my[indexMy.leaf1ndex.value] > 0:
|
|
35
|
+
if my[indexMy.leaf1ndex.value] <= 1 or track[indexTrack.leafBelow.value, 0] == 1:
|
|
36
|
+
if my[indexMy.leaf1ndex.value] > the[indexThe.leavesTotal.value]:
|
|
37
|
+
foldsTotal[my[indexMy.taskIndex.value]] += the[indexThe.leavesTotal.value]
|
|
38
|
+
else:
|
|
39
|
+
my[indexMy.dimensionsUnconstrained.value] = 0
|
|
40
|
+
my[indexMy.gap1ndexCeiling.value] = track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value] - 1]
|
|
41
|
+
my[indexMy.dimension1ndex.value] = 1
|
|
42
|
+
while my[indexMy.dimension1ndex.value] <= the[indexThe.dimensionsTotal.value]:
|
|
43
|
+
if connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], my[indexMy.leaf1ndex.value]] == my[indexMy.leaf1ndex.value]:
|
|
44
|
+
my[indexMy.dimensionsUnconstrained.value] += 1
|
|
45
|
+
else:
|
|
46
|
+
my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], my[indexMy.leaf1ndex.value]]
|
|
47
|
+
while my[indexMy.leafConnectee.value] != my[indexMy.leaf1ndex.value]:
|
|
48
|
+
if ifComputationDivisions(my, the):
|
|
49
|
+
gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.leafConnectee.value]
|
|
50
|
+
if track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] == 0:
|
|
51
|
+
my[indexMy.gap1ndexCeiling.value] += 1
|
|
52
|
+
track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] += 1
|
|
53
|
+
my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], track[indexTrack.leafBelow.value, my[indexMy.leafConnectee.value]]]
|
|
54
|
+
my[indexMy.dimension1ndex.value] += 1
|
|
55
|
+
if insertUnconstrainedLeaf(my, the, Z0Z_initializeUnconstrainedLeaf):
|
|
56
|
+
my[indexMy.indexLeaf.value] = 0
|
|
57
|
+
while my[indexMy.indexLeaf.value] < my[indexMy.leaf1ndex.value]:
|
|
58
|
+
gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.indexLeaf.value]
|
|
59
|
+
my[indexMy.gap1ndexCeiling.value] += 1
|
|
60
|
+
my[indexMy.indexLeaf.value] += 1
|
|
61
|
+
my[indexMy.indexMiniGap.value] = my[indexMy.gap1ndex.value]
|
|
62
|
+
while my[indexMy.indexMiniGap.value] < my[indexMy.gap1ndexCeiling.value]:
|
|
63
|
+
gapsWhere[my[indexMy.gap1ndex.value]] = gapsWhere[my[indexMy.indexMiniGap.value]]
|
|
64
|
+
if track[indexTrack.countDimensionsGapped.value, gapsWhere[my[indexMy.indexMiniGap.value]]] == the[indexThe.dimensionsTotal.value] - my[indexMy.dimensionsUnconstrained.value]:
|
|
65
|
+
my[indexMy.gap1ndex.value] += 1
|
|
66
|
+
track[indexTrack.countDimensionsGapped.value, gapsWhere[my[indexMy.indexMiniGap.value]]] = 0
|
|
67
|
+
my[indexMy.indexMiniGap.value] += 1
|
|
68
|
+
while my[indexMy.leaf1ndex.value] > 0 and my[indexMy.gap1ndex.value] == track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value] - 1]:
|
|
69
|
+
my[indexMy.leaf1ndex.value] -= 1
|
|
70
|
+
track[indexTrack.leafBelow.value, track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]] = track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]
|
|
71
|
+
track[indexTrack.leafAbove.value, track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]] = track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]
|
|
72
|
+
if my[indexMy.leaf1ndex.value] > 0:
|
|
73
|
+
my[indexMy.gap1ndex.value] -= 1
|
|
74
|
+
track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]] = gapsWhere[my[indexMy.gap1ndex.value]]
|
|
75
|
+
track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]] = track[indexTrack.leafBelow.value, track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]]
|
|
76
|
+
track[indexTrack.leafBelow.value, track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]] = my[indexMy.leaf1ndex.value]
|
|
77
|
+
track[indexTrack.leafAbove.value, track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]] = my[indexMy.leaf1ndex.value]
|
|
78
|
+
track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value]] = my[indexMy.gap1ndex.value]
|
|
79
|
+
my[indexMy.leaf1ndex.value] += 1
|
|
80
|
+
if initializationConditionUnconstrainedLeaf(my, Z0Z_initializeUnconstrainedLeaf):
|
|
81
|
+
break
|
|
82
|
+
return foldsTotal, my, gapsWhere, track
|
|
83
|
+
|
|
84
|
+
@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
|
+
|
|
87
|
+
stateFoldsSubTotal = foldsTotal.copy()
|
|
88
|
+
stateMy = my.copy()
|
|
89
|
+
statePotentialGaps = gapsWhere.copy()
|
|
90
|
+
stateTrack = track.copy()
|
|
91
|
+
|
|
92
|
+
for indexSherpa in numba.prange(the[indexThe.taskDivisions.value]):
|
|
93
|
+
my = stateMy.copy()
|
|
94
|
+
my[indexMy.taskIndex.value] = indexSherpa
|
|
95
|
+
foldsSubTotal, _1, _2, _3 = doWhile(connectionGraph, stateFoldsSubTotal.copy(), my, statePotentialGaps.copy(), the, stateTrack.copy(), Z0Z_initializeUnconstrainedLeaf=False)
|
|
96
|
+
|
|
97
|
+
foldsTotal[indexSherpa] = foldsSubTotal[indexSherpa]
|
|
98
|
+
|
|
99
|
+
return foldsTotal
|
|
100
|
+
|
|
101
|
+
@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):
|
|
105
|
+
if obviousFlagForNumba:
|
|
106
|
+
foldsTotal, _1, _2, _3 = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, Z0Z_initializeUnconstrainedLeaf=False)
|
|
107
|
+
else:
|
|
108
|
+
foldsTotal = doTaskIndices(connectionGraph, foldsTotal, my, gapsWhere, the, track)
|
|
109
|
+
|
|
110
|
+
return foldsTotal
|
|
111
|
+
|
|
112
|
+
@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
|
+
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:
|
|
114
|
+
|
|
115
|
+
_0, my, gapsWhere, track = doWhile(connectionGraph, foldsTotal, my, gapsWhere, the, track, Z0Z_initializeUnconstrainedLeaf=True)
|
|
116
|
+
|
|
117
|
+
obviousFlagForNumba = the[indexThe.taskDivisions.value] == int(False)
|
|
118
|
+
|
|
119
|
+
foldsTotal = countFoldsCompileBranch(connectionGraph, foldsTotal, my, gapsWhere, the, track, obviousFlagForNumba)
|
|
120
|
+
|
|
121
|
+
return numpy.sum(foldsTotal).item()
|