mapFolding 0.2.3__py3-none-any.whl → 0.2.5__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/__init__.py +1 -1
- mapFolding/babbage.py +9 -4
- mapFolding/beDRY.py +47 -6
- mapFolding/benchmarks/benchmarking.py +3 -2
- mapFolding/importSelector.py +7 -0
- mapFolding/lovelace.py +92 -96
- mapFolding/{JAX/lunnanJAX.py → reference/jax.py} +2 -0
- mapFolding/someAssemblyRequired/inlineAfunction.py +152 -0
- mapFolding/someAssemblyRequired/jobsAndTasks.py +47 -0
- mapFolding/someAssemblyRequired/makeNuitkaSource.py +99 -0
- mapFolding/someAssemblyRequired/makeNumbaJob.py +121 -0
- mapFolding/startHere.py +8 -28
- mapFolding/theSSOT.py +13 -5
- {mapFolding-0.2.3.dist-info → mapFolding-0.2.5.dist-info}/METADATA +8 -6
- mapFolding-0.2.5.dist-info/RECORD +33 -0
- tests/conftest.py +8 -1
- tests/test_other.py +158 -88
- mapFolding/JAX/taskJAX.py +0 -313
- mapFolding/benchmarks/test_benchmarks.py +0 -74
- mapFolding-0.2.3.dist-info/RECORD +0 -30
- {mapFolding-0.2.3.dist-info → mapFolding-0.2.5.dist-info}/WHEEL +0 -0
- {mapFolding-0.2.3.dist-info → mapFolding-0.2.5.dist-info}/entry_points.txt +0 -0
- {mapFolding-0.2.3.dist-info → mapFolding-0.2.5.dist-info}/top_level.txt +0 -0
mapFolding/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from .theSSOT import *
|
|
2
2
|
from Z0Z_tools import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
3
|
-
from .beDRY import getFilenameFoldsTotal, outfitCountFolds
|
|
3
|
+
from .beDRY import getFilenameFoldsTotal, getPathFilenameFoldsTotal, outfitCountFolds, saveFoldsTotal
|
|
4
4
|
from .startHere import countFolds
|
|
5
5
|
from .oeis import oeisIDfor_n, getOEISids, clearOEIScache
|
|
6
6
|
|
mapFolding/babbage.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from mapFolding.
|
|
1
|
+
from mapFolding.importSelector import countSequential, countParallel, countInitialize
|
|
2
|
+
from mapFolding import indexThe
|
|
2
3
|
from numpy import integer
|
|
3
4
|
from numpy.typing import NDArray
|
|
4
5
|
from typing import Any, Tuple
|
|
@@ -25,6 +26,10 @@ def _countFolds(connectionGraph: NDArray[integer[Any]], foldsSubTotals: NDArray[
|
|
|
25
26
|
- and just a few dozen-jillion other things.
|
|
26
27
|
|
|
27
28
|
"""
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
# print("babbage")
|
|
30
|
+
countInitialize(connectionGraph=connectionGraph, gapsWhere=gapsWhere, my=my, the=the, track=track)
|
|
31
|
+
|
|
32
|
+
if the[indexThe.taskDivisions.value] > 0:
|
|
33
|
+
countParallel(connectionGraph=connectionGraph, foldsSubTotals=foldsSubTotals, gapsWherePARALLEL=gapsWhere, myPARALLEL=my, the=the, trackPARALLEL=track)
|
|
34
|
+
else:
|
|
35
|
+
countSequential(connectionGraph=connectionGraph, foldsSubTotals=foldsSubTotals, gapsWhere=gapsWhere, my=my, the=the, track=track)
|
mapFolding/beDRY.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""A relatively stable API for oft-needed functionality."""
|
|
2
|
-
from mapFolding import dtypeDefault, dtypeLarge
|
|
2
|
+
from mapFolding import dtypeDefault, dtypeLarge, pathJobDEFAULT
|
|
3
3
|
from mapFolding import indexMy, indexThe, indexTrack, computationState
|
|
4
4
|
from mapFolding import intInnit, defineConcurrencyLimit, oopsieKwargsie
|
|
5
5
|
from numpy import integer
|
|
@@ -7,10 +7,12 @@ from numpy.typing import NDArray
|
|
|
7
7
|
from typing import Any, List, Optional, Sequence, Type, Union
|
|
8
8
|
import numba
|
|
9
9
|
import numpy
|
|
10
|
+
import os
|
|
11
|
+
import pathlib
|
|
10
12
|
import sys
|
|
11
13
|
|
|
12
14
|
def getFilenameFoldsTotal(listDimensions: Sequence[int]) -> str:
|
|
13
|
-
return str(sorted(listDimensions)).replace(' ', '') + '.foldsTotal'
|
|
15
|
+
return str(sorted(listDimensions)).replace(', ', 'x') + '.foldsTotal'
|
|
14
16
|
|
|
15
17
|
def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
16
18
|
"""
|
|
@@ -36,6 +38,14 @@ def getLeavesTotal(listDimensions: Sequence[int]) -> int:
|
|
|
36
38
|
|
|
37
39
|
return productDimensions
|
|
38
40
|
|
|
41
|
+
def getPathFilenameFoldsTotal(listDimensions: Sequence[int], pathishWriteFoldsTotal: Optional[Union[str, os.PathLike[str]]] = None) -> pathlib.Path:
|
|
42
|
+
pathFilenameFoldsTotal = pathlib.Path(pathishWriteFoldsTotal) if pathishWriteFoldsTotal is not None else pathJobDEFAULT
|
|
43
|
+
if pathFilenameFoldsTotal.is_dir():
|
|
44
|
+
filenameFoldsTotalDEFAULT = getFilenameFoldsTotal(listDimensions)
|
|
45
|
+
pathFilenameFoldsTotal = pathFilenameFoldsTotal / filenameFoldsTotalDEFAULT
|
|
46
|
+
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
return pathFilenameFoldsTotal
|
|
48
|
+
|
|
39
49
|
def getTaskDivisions(computationDivisions: Optional[Union[int, str]], concurrencyLimit: int, CPUlimit: Optional[Union[bool, float, int]], listDimensions: Sequence[int]):
|
|
40
50
|
"""
|
|
41
51
|
Determines whether or how to divide the computation into tasks.
|
|
@@ -148,7 +158,7 @@ def makeDataContainer(shape, datatype: Optional[Type] = None):
|
|
|
148
158
|
datatype = dtypeDefault
|
|
149
159
|
return numpy.zeros(shape, dtype=datatype)
|
|
150
160
|
|
|
151
|
-
def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Type]) -> computationState:
|
|
161
|
+
def outfitCountFolds(listDimensions: Sequence[int], computationDivisions: Optional[Union[int, str]] = None, CPUlimit: Optional[Union[bool, float, int]] = None, **keywordArguments: Optional[Type[Any]]) -> computationState:
|
|
152
162
|
"""
|
|
153
163
|
Initializes and configures the computation state for map folding computations.
|
|
154
164
|
|
|
@@ -230,11 +240,42 @@ def parseDimensions(dimensions: Sequence[int], parameterName: str = 'unnamed par
|
|
|
230
240
|
raise ValueError(f"Dimension {dimension} must be non-negative")
|
|
231
241
|
listNonNegative.append(dimension)
|
|
232
242
|
|
|
233
|
-
if not listNonNegative:
|
|
234
|
-
raise ValueError("At least one dimension must be non-negative")
|
|
235
|
-
|
|
236
243
|
return listNonNegative
|
|
237
244
|
|
|
245
|
+
import tempfile
|
|
246
|
+
import shutil
|
|
247
|
+
import logging
|
|
248
|
+
import os
|
|
249
|
+
def saveFoldsTotal(pathFilename: Union[str, os.PathLike[str]], foldsTotal: int) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Save foldsTotal with multiple fallback mechanisms.
|
|
252
|
+
|
|
253
|
+
Parameters:
|
|
254
|
+
pathFilename: Target save location
|
|
255
|
+
foldsTotal: Critical computed value to save
|
|
256
|
+
"""
|
|
257
|
+
"""Thoughts
|
|
258
|
+
Everything in a try block
|
|
259
|
+
Save it multiple times with multiple packages
|
|
260
|
+
no need for context managers, especially because they can cause errors"""
|
|
261
|
+
try:
|
|
262
|
+
pathFilenameFoldsTotal = pathlib.Path(pathFilename)
|
|
263
|
+
pathFilenameFoldsTotal.parent.mkdir(parents=True, exist_ok=True)
|
|
264
|
+
pathFilenameFoldsTotal.write_text(str(foldsTotal))
|
|
265
|
+
except Exception as ERRORmessage:
|
|
266
|
+
try:
|
|
267
|
+
print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
|
|
268
|
+
print(ERRORmessage)
|
|
269
|
+
print(f"\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n\n{foldsTotal=}\n\nfoldsTotal foldsTotal foldsTotal foldsTotal foldsTotal\n")
|
|
270
|
+
randomnessPlanB = (int(str(foldsTotal).strip()[-1]) + 1) * ['YO_']
|
|
271
|
+
filenameInfixUnique = ''.join(randomnessPlanB)
|
|
272
|
+
import os
|
|
273
|
+
pathFilenamePlanB = os.path.join(os.getcwd(), 'foldsTotal' + filenameInfixUnique + '.txt')
|
|
274
|
+
open(pathFilenamePlanB, 'w').write(str(foldsTotal))
|
|
275
|
+
print(str(pathFilenamePlanB))
|
|
276
|
+
except:
|
|
277
|
+
print(foldsTotal)
|
|
278
|
+
|
|
238
279
|
def setCPUlimit(CPUlimit: Union[bool, float, int, None]) -> int:
|
|
239
280
|
"""Sets CPU limit for Numba concurrent operations. Note that it can only affect Numba-jitted functions that have not yet been imported.
|
|
240
281
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"""An incompetent benchmarking module for mapFolding."""
|
|
2
2
|
from typing import Callable
|
|
3
|
+
import multiprocessing
|
|
3
4
|
import numpy
|
|
4
5
|
import pathlib
|
|
5
6
|
import time
|
|
@@ -57,7 +58,7 @@ def runBenchmarks(benchmarkIterations: int = 30) -> None:
|
|
|
57
58
|
listCartesianProduct = list(itertools.product(listParametersOEIS, range(benchmarkIterations)))
|
|
58
59
|
with ProcessPoolExecutor(max_workers) as concurrencyManager:
|
|
59
60
|
listConcurrency = [concurrencyManager.submit(oeisIDfor_n, *parameters[0]) for parameters in listCartesianProduct]
|
|
60
|
-
for
|
|
61
|
+
for _complete in tqdm(as_completed(listConcurrency), total=len(listCartesianProduct)):
|
|
61
62
|
pass
|
|
62
63
|
|
|
63
64
|
if __name__ == '__main__':
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from mapFolding.lovelace import countSequential
|
|
2
|
+
from mapFolding.lovelace import countParallel
|
|
3
|
+
from mapFolding.lovelace import countInitialize
|
|
4
|
+
|
|
5
|
+
# from mapFolding.countSequential import countSequential
|
|
6
|
+
# from mapFolding.countParallel import countParallel
|
|
7
|
+
# from mapFolding.countInitialize import countInitialize
|
mapFolding/lovelace.py
CHANGED
|
@@ -1,100 +1,118 @@
|
|
|
1
1
|
from mapFolding import indexMy, indexThe, indexTrack
|
|
2
|
-
from numpy import integer
|
|
3
|
-
from numpy.typing import NDArray
|
|
4
|
-
from typing import Any
|
|
5
2
|
import numba
|
|
6
|
-
import numpy
|
|
7
3
|
|
|
8
|
-
|
|
4
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
5
|
+
def activeGapIncrement(my):
|
|
9
6
|
my[indexMy.gap1ndex.value] += 1
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
9
|
+
def activeLeafGreaterThan0Condition(my):
|
|
12
10
|
return my[indexMy.leaf1ndex.value] > 0
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
@numba.jit((numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
13
|
+
def activeLeafGreaterThanLeavesTotalCondition(my, the):
|
|
15
14
|
return my[indexMy.leaf1ndex.value] > the[indexThe.leavesTotal.value]
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
17
|
+
def activeLeafIsTheFirstLeafCondition(my):
|
|
18
18
|
return my[indexMy.leaf1ndex.value] <= 1
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def allDimensionsAreUnconstrained(my: NDArray[integer[Any]], the: NDArray[integer[Any]]):
|
|
20
|
+
@numba.jit((numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
21
|
+
def allDimensionsAreUnconstrained(my, the):
|
|
24
22
|
return my[indexMy.dimensionsUnconstrained.value] == the[indexThe.dimensionsTotal.value]
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
@numba.jit((numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
25
|
+
def backtrack(my, track):
|
|
27
26
|
my[indexMy.leaf1ndex.value] -= 1
|
|
28
27
|
track[indexTrack.leafBelow.value, track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]] = track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]
|
|
29
28
|
track[indexTrack.leafAbove.value, track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]]] = track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
@numba.jit((numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
31
|
+
def backtrackCondition(my, track):
|
|
32
32
|
return my[indexMy.leaf1ndex.value] > 0 and my[indexMy.gap1ndex.value] == track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value] - 1]
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
35
|
+
def gap1ndexCeilingIncrement(my):
|
|
36
|
+
my[indexMy.gap1ndexCeiling.value] += 1
|
|
37
|
+
|
|
38
|
+
@numba.jit((numba.int64[::1],numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
39
|
+
def countGaps(gapsWhere, my, track):
|
|
35
40
|
gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.leafConnectee.value]
|
|
36
41
|
if track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] == 0:
|
|
37
42
|
gap1ndexCeilingIncrement(my=my)
|
|
38
43
|
track[indexTrack.countDimensionsGapped.value, my[indexMy.leafConnectee.value]] += 1
|
|
39
44
|
|
|
40
|
-
|
|
45
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
46
|
+
def dimension1ndexIncrement(my):
|
|
41
47
|
my[indexMy.dimension1ndex.value] += 1
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
50
|
+
def dimensionsUnconstrainedCondition(connectionGraph, my):
|
|
44
51
|
return connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], my[indexMy.leaf1ndex.value]] == my[indexMy.leaf1ndex.value]
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
54
|
+
def dimensionsUnconstrainedIncrement(my):
|
|
47
55
|
my[indexMy.dimensionsUnconstrained.value] += 1
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
@numba.jit((numba.int64[::1],numba.int64[::1],numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
58
|
+
def filterCommonGaps(gapsWhere, my, the, track):
|
|
50
59
|
gapsWhere[my[indexMy.gap1ndex.value]] = gapsWhere[my[indexMy.indexMiniGap.value]]
|
|
51
60
|
if track[indexTrack.countDimensionsGapped.value, gapsWhere[my[indexMy.indexMiniGap.value]]] == the[indexThe.dimensionsTotal.value] - my[indexMy.dimensionsUnconstrained.value]:
|
|
52
61
|
activeGapIncrement(my=my)
|
|
53
62
|
track[indexTrack.countDimensionsGapped.value, gapsWhere[my[indexMy.indexMiniGap.value]]] = 0
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
@numba.jit((numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
65
|
+
def findGapsInitializeVariables(my, track):
|
|
56
66
|
my[indexMy.dimensionsUnconstrained.value] = 0
|
|
57
67
|
my[indexMy.gap1ndexCeiling.value] = track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value] - 1]
|
|
58
68
|
my[indexMy.dimension1ndex.value] = 1
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
@numba.jit((numba.int64[::1],numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
71
|
+
def foldsSubTotalIncrement(foldsSubTotals, my, the):
|
|
61
72
|
foldsSubTotals[my[indexMy.taskIndex.value]] += the[indexThe.leavesTotal.value]
|
|
62
73
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def indexMiniGapIncrement(my: NDArray[integer[Any]]):
|
|
74
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
75
|
+
def indexMiniGapIncrement(my):
|
|
67
76
|
my[indexMy.indexMiniGap.value] += 1
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
79
|
+
def indexMiniGapInitialization(my):
|
|
70
80
|
my[indexMy.indexMiniGap.value] = my[indexMy.gap1ndex.value]
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
@numba.jit((numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
83
|
+
def insertUnconstrainedLeaf(gapsWhere, my):
|
|
73
84
|
my[indexMy.indexLeaf.value] = 0
|
|
74
85
|
while my[indexMy.indexLeaf.value] < my[indexMy.leaf1ndex.value]:
|
|
75
86
|
gapsWhere[my[indexMy.gap1ndexCeiling.value]] = my[indexMy.indexLeaf.value]
|
|
76
87
|
my[indexMy.gap1ndexCeiling.value] += 1
|
|
77
88
|
my[indexMy.indexLeaf.value] += 1
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
@numba.jit((numba.int64[:,::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
91
|
+
def leafBelowSentinelIs1Condition(track):
|
|
80
92
|
return track[indexTrack.leafBelow.value, 0] == 1
|
|
81
93
|
|
|
82
|
-
|
|
94
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
95
|
+
def leafConnecteeInitialization(connectionGraph, my):
|
|
83
96
|
my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], my[indexMy.leaf1ndex.value]]
|
|
84
97
|
|
|
85
|
-
|
|
98
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
99
|
+
def leafConnecteeUpdate(connectionGraph, my, track):
|
|
86
100
|
my[indexMy.leafConnectee.value] = connectionGraph[my[indexMy.dimension1ndex.value], my[indexMy.leaf1ndex.value], track[indexTrack.leafBelow.value, my[indexMy.leafConnectee.value]]]
|
|
87
101
|
|
|
88
|
-
|
|
102
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
103
|
+
def loopingLeavesConnectedToActiveLeaf(my):
|
|
89
104
|
return my[indexMy.leafConnectee.value] != my[indexMy.leaf1ndex.value]
|
|
90
105
|
|
|
91
|
-
|
|
106
|
+
@numba.jit((numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
107
|
+
def loopingTheDimensions(my, the):
|
|
92
108
|
return my[indexMy.dimension1ndex.value] <= the[indexThe.dimensionsTotal.value]
|
|
93
109
|
|
|
94
|
-
|
|
110
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
111
|
+
def loopingToActiveGapCeiling(my):
|
|
95
112
|
return my[indexMy.indexMiniGap.value] < my[indexMy.gap1ndexCeiling.value]
|
|
96
113
|
|
|
97
|
-
|
|
114
|
+
@numba.jit((numba.int64[::1],numba.int64[::1],numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
115
|
+
def placeLeaf(gapsWhere, my, track):
|
|
98
116
|
my[indexMy.gap1ndex.value] -= 1
|
|
99
117
|
track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]] = gapsWhere[my[indexMy.gap1ndex.value]]
|
|
100
118
|
track[indexTrack.leafBelow.value, my[indexMy.leaf1ndex.value]] = track[indexTrack.leafBelow.value, track[indexTrack.leafAbove.value, my[indexMy.leaf1ndex.value]]]
|
|
@@ -103,20 +121,16 @@ def placeLeaf(gapsWhere: NDArray[integer[Any]], my: NDArray[integer[Any]], track
|
|
|
103
121
|
track[indexTrack.gapRangeStart.value, my[indexMy.leaf1ndex.value]] = my[indexMy.gap1ndex.value]
|
|
104
122
|
my[indexMy.leaf1ndex.value] += 1
|
|
105
123
|
|
|
106
|
-
|
|
124
|
+
@numba.jit((numba.int64[::1],), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
125
|
+
def placeLeafCondition(my):
|
|
107
126
|
return my[indexMy.leaf1ndex.value] > 0
|
|
108
127
|
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
@numba.jit((numba.int64[::1],numba.int64[::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
129
|
+
def thereAreComputationDivisionsYouMightSkip(my, the):
|
|
130
|
+
return my[indexMy.leaf1ndex.value] != the[indexThe.taskDivisions.value] or my[indexMy.leafConnectee.value] % the[indexThe.taskDivisions.value] == my[indexMy.taskIndex.value]
|
|
111
131
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return True
|
|
115
|
-
if taskIndexCondition(my=my, the=the):
|
|
116
|
-
return True
|
|
117
|
-
return False
|
|
118
|
-
|
|
119
|
-
def initialize(connectionGraph: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], my: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]):
|
|
132
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1], numba.int64[::1], numba.int64[::1], numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
133
|
+
def countInitialize(connectionGraph, gapsWhere, my, the, track):
|
|
120
134
|
while activeLeafGreaterThan0Condition(my=my):
|
|
121
135
|
if activeLeafIsTheFirstLeafCondition(my=my) or leafBelowSentinelIs1Condition(track=track):
|
|
122
136
|
findGapsInitializeVariables(my=my, track=track)
|
|
@@ -138,35 +152,10 @@ def initialize(connectionGraph: NDArray[integer[Any]], gapsWhere: NDArray[intege
|
|
|
138
152
|
if placeLeafCondition(my=my):
|
|
139
153
|
placeLeaf(gapsWhere=gapsWhere, my=my, track=track)
|
|
140
154
|
if my[indexMy.gap1ndex.value] > 0:
|
|
141
|
-
|
|
155
|
+
return
|
|
142
156
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if activeLeafIsTheFirstLeafCondition(my=my) or leafBelowSentinelIs1Condition(track=track):
|
|
146
|
-
if activeLeafGreaterThanLeavesTotalCondition(my=my, the=the):
|
|
147
|
-
foldsSubTotalIncrement(foldsSubTotals=foldsSubTotals, my=my, the=the)
|
|
148
|
-
else:
|
|
149
|
-
findGapsInitializeVariables(my=my, track=track)
|
|
150
|
-
while loopingTheDimensions(my=my, the=the):
|
|
151
|
-
if dimensionsUnconstrainedCondition(connectionGraph=connectionGraph, my=my):
|
|
152
|
-
dimensionsUnconstrainedIncrement(my=my)
|
|
153
|
-
else:
|
|
154
|
-
leafConnecteeInitialization(connectionGraph=connectionGraph, my=my)
|
|
155
|
-
while loopingLeavesConnectedToActiveLeaf(my=my):
|
|
156
|
-
if thereAreComputationDivisionsYouMightSkip(my=my, the=the):
|
|
157
|
-
countGaps(gapsWhere=gapsWhere, my=my, track=track)
|
|
158
|
-
leafConnecteeUpdate(connectionGraph=connectionGraph, my=my, track=track)
|
|
159
|
-
dimension1ndexIncrement(my=my)
|
|
160
|
-
indexMiniGapInitialization(my=my)
|
|
161
|
-
while loopingToActiveGapCeiling(my=my):
|
|
162
|
-
filterCommonGaps(gapsWhere=gapsWhere, my=my, the=the, track=track)
|
|
163
|
-
indexMiniGapIncrement(my=my)
|
|
164
|
-
while backtrackCondition(my=my, track=track):
|
|
165
|
-
backtrack(my=my, track=track)
|
|
166
|
-
if placeLeafCondition(my=my):
|
|
167
|
-
placeLeaf(gapsWhere=gapsWhere, my=my, track=track)
|
|
168
|
-
|
|
169
|
-
def countSequential(connectionGraph: NDArray[integer[Any]], foldsSubTotals: NDArray[integer[Any]], gapsWhere: NDArray[integer[Any]], my: NDArray[integer[Any]], the: NDArray[integer[Any]], track: NDArray[integer[Any]]):
|
|
157
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1], numba.int64[::1], numba.int64[::1], numba.int64[::1], numba.int64[:,::1]), parallel=False, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
158
|
+
def countSequential(connectionGraph, foldsSubTotals, gapsWhere, my, the, track):
|
|
170
159
|
while activeLeafGreaterThan0Condition(my=my):
|
|
171
160
|
if activeLeafIsTheFirstLeafCondition(my=my) or leafBelowSentinelIs1Condition(track=track):
|
|
172
161
|
if activeLeafGreaterThanLeavesTotalCondition(my=my, the=the):
|
|
@@ -191,27 +180,34 @@ def countSequential(connectionGraph: NDArray[integer[Any]], foldsSubTotals: NDAr
|
|
|
191
180
|
if placeLeafCondition(my=my):
|
|
192
181
|
placeLeaf(gapsWhere=gapsWhere, my=my, track=track)
|
|
193
182
|
|
|
194
|
-
@numba.jit(
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
stateGapsWhere = gapsWhere.copy()
|
|
198
|
-
stateMy = my.copy()
|
|
199
|
-
stateTrack = track.copy()
|
|
200
|
-
|
|
183
|
+
@numba.jit((numba.int64[:,:,::1], numba.int64[::1], numba.int64[::1],numba.int64[::1],numba.int64[::1],numba.int64[:,::1]), parallel=True, boundscheck=False, error_model='numpy', fastmath=True, looplift=False, nogil=True, nopython=True)
|
|
184
|
+
def countParallel(connectionGraph, foldsSubTotals, gapsWherePARALLEL, myPARALLEL, the, trackPARALLEL):
|
|
201
185
|
for indexSherpa in numba.prange(the[indexThe.taskDivisions.value]):
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
186
|
+
gapsWhere = gapsWherePARALLEL.copy()
|
|
187
|
+
my = myPARALLEL.copy()
|
|
188
|
+
my[indexMy.taskIndex.value] = indexSherpa
|
|
189
|
+
track = trackPARALLEL.copy()
|
|
190
|
+
while activeLeafGreaterThan0Condition(my=my):
|
|
191
|
+
if activeLeafIsTheFirstLeafCondition(my=my) or leafBelowSentinelIs1Condition(track=track):
|
|
192
|
+
if activeLeafGreaterThanLeavesTotalCondition(my=my, the=the):
|
|
193
|
+
foldsSubTotalIncrement(foldsSubTotals=foldsSubTotals, my=my, the=the)
|
|
194
|
+
else:
|
|
195
|
+
findGapsInitializeVariables(my=my, track=track)
|
|
196
|
+
while loopingTheDimensions(my=my, the=the):
|
|
197
|
+
if dimensionsUnconstrainedCondition(connectionGraph=connectionGraph, my=my):
|
|
198
|
+
dimensionsUnconstrainedIncrement(my=my)
|
|
199
|
+
else:
|
|
200
|
+
leafConnecteeInitialization(connectionGraph=connectionGraph, my=my)
|
|
201
|
+
while loopingLeavesConnectedToActiveLeaf(my=my):
|
|
202
|
+
if thereAreComputationDivisionsYouMightSkip(my=my, the=the):
|
|
203
|
+
countGaps(gapsWhere=gapsWhere, my=my, track=track)
|
|
204
|
+
leafConnecteeUpdate(connectionGraph=connectionGraph, my=my, track=track)
|
|
205
|
+
dimension1ndexIncrement(my=my)
|
|
206
|
+
indexMiniGapInitialization(my=my)
|
|
207
|
+
while loopingToActiveGapCeiling(my=my):
|
|
208
|
+
filterCommonGaps(gapsWhere=gapsWhere, my=my, the=the, track=track)
|
|
209
|
+
indexMiniGapIncrement(my=my)
|
|
210
|
+
while backtrackCondition(my=my, track=track):
|
|
211
|
+
backtrack(my=my, track=track)
|
|
212
|
+
if placeLeafCondition(my=my):
|
|
213
|
+
placeLeaf(gapsWhere=gapsWhere, my=my, track=track)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""I was able to implement the algorithm with JAX, but I didn't see an advantage and it's a pain in the ass.
|
|
2
|
+
I don't maintain this module."""
|
|
1
3
|
from mapFolding import validateListDimensions, getLeavesTotal, makeConnectionGraph
|
|
2
4
|
from typing import List, Tuple
|
|
3
5
|
import jax
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from mapFolding import indexMy, indexThe, indexTrack
|
|
2
|
+
import ast
|
|
3
|
+
import copy
|
|
4
|
+
import pathlib
|
|
5
|
+
|
|
6
|
+
def getDictionaryEnumValues():
|
|
7
|
+
dictionaryEnumValues = {}
|
|
8
|
+
for enumIndex in [indexMy, indexThe, indexTrack]:
|
|
9
|
+
for memberName, memberValue in enumIndex._member_map_.items():
|
|
10
|
+
dictionaryEnumValues[f"{enumIndex.__name__}.{memberName}.value"] = memberValue.value
|
|
11
|
+
return dictionaryEnumValues
|
|
12
|
+
|
|
13
|
+
class RecursiveInlinerWithEnum(ast.NodeTransformer):
|
|
14
|
+
def __init__(self, dictionaryFunctions, dictionaryEnumValues):
|
|
15
|
+
self.dictionaryFunctions = dictionaryFunctions
|
|
16
|
+
self.dictionaryEnumValues = dictionaryEnumValues
|
|
17
|
+
self.processed = set() # Track processed functions to avoid infinite recursion
|
|
18
|
+
|
|
19
|
+
def inlineFunctionBody(self, functionName):
|
|
20
|
+
if functionName in self.processed:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
self.processed.add(functionName)
|
|
24
|
+
inlineDefinition = self.dictionaryFunctions[functionName]
|
|
25
|
+
# Recursively process the function body
|
|
26
|
+
for node in ast.walk(inlineDefinition):
|
|
27
|
+
self.visit(node)
|
|
28
|
+
return inlineDefinition
|
|
29
|
+
|
|
30
|
+
def visit_Attribute(self, node):
|
|
31
|
+
# Substitute enum identifiers (e.g., indexMy.leaf1ndex.value)
|
|
32
|
+
if isinstance(node.value, ast.Attribute) and isinstance(node.value.value, ast.Name):
|
|
33
|
+
enumPath = f"{node.value.value.id}.{node.value.attr}.{node.attr}"
|
|
34
|
+
if enumPath in self.dictionaryEnumValues:
|
|
35
|
+
return ast.Constant(value=self.dictionaryEnumValues[enumPath])
|
|
36
|
+
return self.generic_visit(node)
|
|
37
|
+
|
|
38
|
+
def visit_Call(self, node):
|
|
39
|
+
callNode = self.generic_visit(node)
|
|
40
|
+
if isinstance(callNode, ast.Call) and isinstance(callNode.func, ast.Name) and callNode.func.id in self.dictionaryFunctions:
|
|
41
|
+
inlineDefinition = self.inlineFunctionBody(callNode.func.id)
|
|
42
|
+
if inlineDefinition and inlineDefinition.body:
|
|
43
|
+
lastStmt = inlineDefinition.body[-1]
|
|
44
|
+
if isinstance(lastStmt, ast.Return) and lastStmt.value is not None:
|
|
45
|
+
return self.visit(lastStmt.value)
|
|
46
|
+
elif isinstance(lastStmt, ast.Expr) and lastStmt.value is not None:
|
|
47
|
+
return self.visit(lastStmt.value)
|
|
48
|
+
return None
|
|
49
|
+
return callNode
|
|
50
|
+
|
|
51
|
+
def visit_Expr(self, node):
|
|
52
|
+
if isinstance(node.value, ast.Call):
|
|
53
|
+
if isinstance(node.value.func, ast.Name) and node.value.func.id in self.dictionaryFunctions:
|
|
54
|
+
inlineDefinition = self.inlineFunctionBody(node.value.func.id)
|
|
55
|
+
if inlineDefinition:
|
|
56
|
+
return [self.visit(stmt) for stmt in inlineDefinition.body]
|
|
57
|
+
return self.generic_visit(node)
|
|
58
|
+
|
|
59
|
+
def findRequiredImports(node):
|
|
60
|
+
"""Find all modules that need to be imported based on AST analysis.
|
|
61
|
+
NOTE: due to hardcoding, this is a glorified regex. No, wait, this is less versatile than regex."""
|
|
62
|
+
requiredImports = set()
|
|
63
|
+
|
|
64
|
+
class ImportFinder(ast.NodeVisitor):
|
|
65
|
+
def visit_Name(self, node):
|
|
66
|
+
if node.id in {'numba'}:
|
|
67
|
+
requiredImports.add(node.id)
|
|
68
|
+
self.generic_visit(node)
|
|
69
|
+
|
|
70
|
+
def visitDecorator(self, node):
|
|
71
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
72
|
+
if node.func.id == 'jit':
|
|
73
|
+
requiredImports.add('numba')
|
|
74
|
+
self.generic_visit(node)
|
|
75
|
+
|
|
76
|
+
ImportFinder().visit(node)
|
|
77
|
+
return requiredImports
|
|
78
|
+
|
|
79
|
+
def generateImports(requiredImports):
|
|
80
|
+
"""Generate import statements based on required modules."""
|
|
81
|
+
importStatements = []
|
|
82
|
+
|
|
83
|
+
# Map of module names to their import statements
|
|
84
|
+
importMapping = {
|
|
85
|
+
'numba': 'import numba',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for moduleName in sorted(requiredImports):
|
|
89
|
+
if moduleName in importMapping:
|
|
90
|
+
importStatements.append(importMapping[moduleName])
|
|
91
|
+
|
|
92
|
+
return '\n'.join(importStatements)
|
|
93
|
+
|
|
94
|
+
def inlineFunctions(sourceCode, targetFunctionName, dictionaryEnumValues):
|
|
95
|
+
dictionaryParsed = ast.parse(sourceCode)
|
|
96
|
+
dictionaryFunctions = {
|
|
97
|
+
element.name: element
|
|
98
|
+
for element in dictionaryParsed.body
|
|
99
|
+
if isinstance(element, ast.FunctionDef)
|
|
100
|
+
}
|
|
101
|
+
nodeTarget = dictionaryFunctions[targetFunctionName]
|
|
102
|
+
nodeInliner = RecursiveInlinerWithEnum(dictionaryFunctions, dictionaryEnumValues)
|
|
103
|
+
nodeInlined = nodeInliner.visit(nodeTarget)
|
|
104
|
+
ast.fix_missing_locations(nodeInlined)
|
|
105
|
+
|
|
106
|
+
# Generate imports
|
|
107
|
+
requiredImports = findRequiredImports(nodeInlined)
|
|
108
|
+
importStatements = generateImports(requiredImports)
|
|
109
|
+
|
|
110
|
+
# Combine imports with inlined code
|
|
111
|
+
inlinedCode = importStatements + '\n\n' + ast.unparse(ast.Module(body=[nodeInlined], type_ignores=[]))
|
|
112
|
+
return inlinedCode
|
|
113
|
+
|
|
114
|
+
def Z0Z_inlineMapFolding():
|
|
115
|
+
dictionaryEnumValues = getDictionaryEnumValues()
|
|
116
|
+
|
|
117
|
+
pathFilenameSource = pathlib.Path("/apps/mapFolding/mapFolding/lovelace.py")
|
|
118
|
+
codeSource = pathFilenameSource.read_text()
|
|
119
|
+
|
|
120
|
+
listCallables = [
|
|
121
|
+
'countInitialize',
|
|
122
|
+
'countParallel',
|
|
123
|
+
'countSequential',
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
listPathFilenamesDestination: list[pathlib.Path] = []
|
|
127
|
+
for callableTarget in listCallables:
|
|
128
|
+
pathFilenameDestination = pathFilenameSource.with_stem(callableTarget)
|
|
129
|
+
codeInlined = inlineFunctions(codeSource, callableTarget, dictionaryEnumValues)
|
|
130
|
+
pathFilenameDestination.write_text(codeInlined)
|
|
131
|
+
listPathFilenamesDestination.append(pathFilenameDestination)
|
|
132
|
+
|
|
133
|
+
listNoNumba = [
|
|
134
|
+
'countInitialize',
|
|
135
|
+
'countSequential',
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
listPathFilenamesNoNumba = []
|
|
139
|
+
for pathFilename in listPathFilenamesDestination:
|
|
140
|
+
if pathFilename.stem in listNoNumba:
|
|
141
|
+
pathFilenameNoNumba = pathFilename.with_name(pathFilename.stem + 'NoNumba' + pathFilename.suffix)
|
|
142
|
+
else:
|
|
143
|
+
continue
|
|
144
|
+
codeNoNumba = pathFilename.read_text()
|
|
145
|
+
for codeLine in copy.copy(codeNoNumba.splitlines()):
|
|
146
|
+
if 'numba' in codeLine:
|
|
147
|
+
codeNoNumba = codeNoNumba.replace(codeLine, '')
|
|
148
|
+
pathFilenameNoNumba.write_text(codeNoNumba)
|
|
149
|
+
listPathFilenamesNoNumba.append(pathFilenameNoNumba)
|
|
150
|
+
|
|
151
|
+
if __name__ == '__main__':
|
|
152
|
+
Z0Z_inlineMapFolding()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Any, Optional, Sequence, Type, Union
|
|
2
|
+
|
|
3
|
+
def Z0Z_makeJob(listDimensions: Sequence[int], **keywordArguments: Optional[Type[Any]]):
|
|
4
|
+
from mapFolding import outfitCountFolds
|
|
5
|
+
stateUniversal = outfitCountFolds(listDimensions, computationDivisions=None, CPUlimit=None, **keywordArguments)
|
|
6
|
+
from mapFolding.countInitialize import countInitialize
|
|
7
|
+
countInitialize(stateUniversal['connectionGraph'], stateUniversal['gapsWhere'], stateUniversal['my'], stateUniversal['the'], stateUniversal['track'])
|
|
8
|
+
from mapFolding import getPathFilenameFoldsTotal
|
|
9
|
+
pathFilenameChopChop = getPathFilenameFoldsTotal(stateUniversal['mapShape'])
|
|
10
|
+
import pathlib
|
|
11
|
+
suffix = pathFilenameChopChop.suffix
|
|
12
|
+
pathJob = pathlib.Path(str(pathFilenameChopChop)[0:-len(suffix)])
|
|
13
|
+
pathJob.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
pathFilenameJob = pathJob / 'stateJob.pkl'
|
|
15
|
+
|
|
16
|
+
pathFilenameFoldsTotal = getPathFilenameFoldsTotal(stateUniversal['mapShape'], pathFilenameJob.parent)
|
|
17
|
+
stateJob = {**stateUniversal, 'pathFilenameFoldsTotal': pathFilenameFoldsTotal}
|
|
18
|
+
|
|
19
|
+
del stateJob['mapShape']
|
|
20
|
+
|
|
21
|
+
import pickle
|
|
22
|
+
pathFilenameJob.write_bytes(pickle.dumps(stateJob))
|
|
23
|
+
return pathFilenameJob
|
|
24
|
+
|
|
25
|
+
def runJob(pathFilename):
|
|
26
|
+
from typing import Final
|
|
27
|
+
import numpy
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
pathFilenameJob = Path(pathFilename)
|
|
30
|
+
from pickle import loads
|
|
31
|
+
stateJob = loads(pathFilenameJob.read_bytes())
|
|
32
|
+
|
|
33
|
+
connectionGraph: numpy.ndarray = stateJob['connectionGraph']
|
|
34
|
+
foldsSubTotals: numpy.ndarray = stateJob['foldsSubTotals']
|
|
35
|
+
gapsWhere: numpy.ndarray = stateJob['gapsWhere']
|
|
36
|
+
my: numpy.ndarray = stateJob['my']
|
|
37
|
+
pathFilenameFoldsTotal: Final[Path] = stateJob['pathFilenameFoldsTotal']
|
|
38
|
+
the: Final[numpy.ndarray] = stateJob['the']
|
|
39
|
+
track: numpy.ndarray = stateJob['track']
|
|
40
|
+
|
|
41
|
+
from mapFolding.countSequentialNoNumba import countSequential
|
|
42
|
+
countSequential(connectionGraph, foldsSubTotals, gapsWhere, my, the, track)
|
|
43
|
+
|
|
44
|
+
print(foldsSubTotals.sum().item())
|
|
45
|
+
Path(pathFilenameFoldsTotal).parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
Path(pathFilenameFoldsTotal).write_text(str(foldsSubTotals.sum().item()))
|
|
47
|
+
print(pathFilenameFoldsTotal)
|