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/oeis.py ADDED
@@ -0,0 +1,299 @@
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
6
+ import pathlib
7
+ import random
8
+ import sys
9
+ import time
10
+ import urllib.request
11
+ import urllib.response
12
+
13
+ if TYPE_CHECKING:
14
+ from typing import TypedDict
15
+ else:
16
+ TypedDict = dict
17
+
18
+ """
19
+ Section: make `settingsOEIS`"""
20
+ class SettingsOEIS(TypedDict):
21
+ # I would prefer to load description dynamically from OEIS, but it's a pita for me
22
+ # to learn how to efficiently implement right now.
23
+ description: str
24
+ getDimensions: Callable[[int], List[int]]
25
+ valuesBenchmark: List[int]
26
+ valuesKnown: Dict[int, int]
27
+ valuesTestValidation: List[int]
28
+ valueUnknown: int
29
+
30
+ settingsOEIShardcodedValues = {
31
+ 'A001415': {
32
+ 'description': 'Number of ways of folding a 2 X n strip of stamps.',
33
+ 'getDimensions': lambda n: sorted([2, n]),
34
+ 'valuesBenchmark': [14],
35
+ 'valuesTestValidation': [0, 1, random.randint(2, 9)],
36
+ },
37
+ 'A001416': {
38
+ 'description': 'Number of ways of folding a 3 X n strip of stamps.',
39
+ 'getDimensions': lambda n: sorted([3, n]),
40
+ 'valuesBenchmark': [9],
41
+ 'valuesTestValidation': [0, 1, random.randint(2, 6)],
42
+ },
43
+ 'A001417': {
44
+ 'description': 'Number of ways of folding a 2 X 2 X ... X 2 n-dimensional map.',
45
+ 'getDimensions': lambda n: [2] * n,
46
+ 'valuesBenchmark': [6],
47
+ 'valuesTestValidation': [0, 1, random.randint(2, 4)],
48
+ },
49
+ 'A195646': {
50
+ 'description': 'Number of ways of folding a 3 X 3 X ... X 3 n-dimensional map.',
51
+ 'getDimensions': lambda n: [3] * n,
52
+ 'valuesBenchmark': [3],
53
+ 'valuesTestValidation': [0, 1, 2],
54
+ },
55
+ 'A001418': {
56
+ 'description': 'Number of ways of folding an n X n sheet of stamps.',
57
+ 'getDimensions': lambda n: [n, n],
58
+ 'valuesBenchmark': [5],
59
+ # offset 1: hypothetically, if I were to load the offset from OEIS, I could use it to
60
+ # determine if a sequence is defined at n=0, which would affect, for example, the valuesTestValidation.
61
+ 'valuesTestValidation': [1, random.randint(2, 4)],
62
+ },
63
+ }
64
+
65
+ oeisIDsImplemented: Final[List[str]] = sorted([oeisID.upper().strip() for oeisID in settingsOEIShardcodedValues.keys()])
66
+ """Directly implemented OEIS IDs; standardized, e.g., 'A001415'."""
67
+
68
+ def _parseBFileOEIS(OEISbFile: str, oeisID: str) -> Dict[int, int]:
69
+ """
70
+ Parses the content of an OEIS b-file for a given sequence ID.
71
+ This function processes a multiline string representing an OEIS b-file and
72
+ creates a dictionary mapping integer indices to their corresponding sequence
73
+ values. The first line of the b-file is expected to contain a comment that
74
+ matches the given sequence ID. If it does not match, a ValueError is raised.
75
+
76
+ Parameters:
77
+ OEISbFile: A multiline string representing an OEIS b-file.
78
+ oeisID: The expected OEIS sequence identifier.
79
+ Returns:
80
+ OEISsequence: A dictionary where each key is an integer index `n` and
81
+ each value is the sequence value `a(n)` corresponding to that index.
82
+ Raises:
83
+ ValueError: If the first line of the file does not indicate the expected
84
+ sequence ID or if the content format is invalid.
85
+ """
86
+ bFileLines = OEISbFile.strip().splitlines()
87
+ # The first line has the sequence ID
88
+ if not bFileLines.pop(0).startswith(f"# {oeisID}"):
89
+ raise ValueError(f"Content does not match sequence {oeisID}")
90
+
91
+ OEISsequence = {}
92
+ for line in bFileLines:
93
+ if line.startswith('#'):
94
+ continue
95
+ n, aOFn = map(int, line.split())
96
+ OEISsequence[n] = aOFn
97
+ return OEISsequence
98
+
99
+ try:
100
+ _pathCache = pathlib.Path(__file__).parent / ".cache"
101
+ except NameError:
102
+ _pathCache = pathlib.Path.home() / ".mapFoldingCache"
103
+
104
+ _formatFilenameCache = "{oeisID}.txt"
105
+
106
+ def _getOEISidValues(oeisID: str) -> Dict[int, int]:
107
+ """
108
+ Retrieves the specified OEIS sequence as a dictionary mapping integer indices
109
+ to their corresponding values.
110
+ This function checks for a cached local copy of the sequence data, using it if
111
+ it has not expired. Otherwise, it fetches the sequence data from the OEIS
112
+ website and writes it to the cache. The parsed data is returned as a dictionary
113
+ mapping each index to its sequence value.
114
+
115
+ Parameters:
116
+ oeisID: The identifier of the OEIS sequence to retrieve.
117
+ Returns:
118
+ OEISsequence: A dictionary where each key is an integer index, `n`, and each
119
+ value is the corresponding "a(n)" from the OEIS entry.
120
+ Raises:
121
+ ValueError: If the cached or downloaded file format is invalid.
122
+ IOError: If there is an error reading from or writing to the local cache.
123
+ """
124
+
125
+ pathFilenameCache = _pathCache / _formatFilenameCache.format(oeisID=oeisID)
126
+ cacheDays = 7
127
+
128
+ tryCache = False
129
+ if pathFilenameCache.exists():
130
+ fileAge = datetime.now() - datetime.fromtimestamp(pathFilenameCache.stat().st_mtime)
131
+ tryCache = fileAge < timedelta(days=cacheDays)
132
+
133
+ if tryCache:
134
+ try:
135
+ OEISbFile = pathFilenameCache.read_text()
136
+ return _parseBFileOEIS(OEISbFile, oeisID)
137
+ except (ValueError, IOError):
138
+ tryCache = False
139
+
140
+ urlOEISbFile = f"https://oeis.org/{oeisID}/b{oeisID[1:]}.txt"
141
+ httpResponse: urllib.response.addinfourl = urllib.request.urlopen(urlOEISbFile)
142
+ OEISbFile = httpResponse.read().decode('utf-8')
143
+
144
+ if not tryCache:
145
+ pathFilenameCache.parent.mkdir(parents=True, exist_ok=True)
146
+ pathFilenameCache.write_text(OEISbFile)
147
+
148
+ return _parseBFileOEIS(OEISbFile, oeisID)
149
+
150
+ def makeSettingsOEIS() -> Dict[str, SettingsOEIS]:
151
+ settingsTarget = {}
152
+ for oeisID in oeisIDsImplemented:
153
+ valuesKnownSherpa = _getOEISidValues(oeisID)
154
+ settingsTarget[oeisID] = SettingsOEIS(
155
+ description = settingsOEIShardcodedValues[oeisID]['description'],
156
+ getDimensions = settingsOEIShardcodedValues[oeisID]['getDimensions'],
157
+ valuesBenchmark = settingsOEIShardcodedValues[oeisID]['valuesBenchmark'],
158
+ valuesKnown = valuesKnownSherpa,
159
+ valuesTestValidation = settingsOEIShardcodedValues[oeisID]['valuesTestValidation'],
160
+ valueUnknown = max(valuesKnownSherpa.keys(), default=0) + 1
161
+ )
162
+ return settingsTarget
163
+
164
+ settingsOEIS: Dict[str, SettingsOEIS] = makeSettingsOEIS()
165
+ """All values and settings for `oeisIDsImplemented`."""
166
+
167
+ """
168
+ Section: private functions"""
169
+
170
+ def _formatHelpText() -> str:
171
+ """Format standardized help text for both CLI and interactive use."""
172
+ exampleOEISid = oeisIDsImplemented[0]
173
+ exampleN = settingsOEIS[exampleOEISid]['valuesTestValidation'][-1]
174
+
175
+ return (
176
+ "\nAvailable OEIS sequences:\n"
177
+ f"{_formatOEISsequenceInfo()}\n"
178
+ "\nUsage examples:\n"
179
+ " Command line:\n"
180
+ f" OEIS_for_n {exampleOEISid} {exampleN}\n"
181
+ " Python:\n"
182
+ " from mapFolding import oeisIDfor_n\n"
183
+ f" foldsTotal = oeisIDfor_n('{exampleOEISid}', {exampleN})"
184
+ )
185
+
186
+ def _formatOEISsequenceInfo() -> str:
187
+ """Format information about available OEIS sequences for display or error messages."""
188
+ return "\n".join(
189
+ f" {oeisID}: {settingsOEIS[oeisID]['description']}"
190
+ for oeisID in oeisIDsImplemented
191
+ )
192
+
193
+ def _validateOEISid(oeisIDcandidate: str):
194
+ """
195
+ Validates an OEIS sequence ID against implemented sequences.
196
+
197
+ If the provided ID is recognized within the application's implemented
198
+ OEIS sequences, the function returns the verified ID in uppercase.
199
+ Otherwise, a KeyError is raised indicating that the sequence is not
200
+ directly supported.
201
+
202
+ Parameters:
203
+ oeisIDcandidate: The OEIS sequence identifier to validate.
204
+
205
+ Returns:
206
+ oeisID: The validated and possibly modified OEIS sequence ID, if recognized.
207
+
208
+ Raises:
209
+ KeyError: If the provided sequence ID is not directly implemented.
210
+ """
211
+ if oeisIDcandidate in oeisIDsImplemented:
212
+ return oeisIDcandidate
213
+ else:
214
+ oeisIDcleaned = str(oeisIDcandidate).upper().strip()
215
+ if oeisIDcleaned in oeisIDsImplemented:
216
+ return oeisIDcleaned
217
+ else:
218
+ raise KeyError(
219
+ f"OEIS ID {oeisIDcandidate} is not directly implemented.\n"
220
+ f"Available sequences:\n{_formatOEISsequenceInfo()}"
221
+ )
222
+
223
+ """
224
+ Section: public functions"""
225
+
226
+ def oeisIDfor_n(oeisID: str, n: int) -> int:
227
+ """
228
+ Calculate a(n) of a sequence from "The On-Line Encyclopedia of Integer Sequences" (OEIS).
229
+
230
+ Parameters:
231
+ oeisID: The ID of the OEIS sequence.
232
+ n: A non-negative integer for which to calculate the sequence value.
233
+
234
+ Returns:
235
+ sequenceValue: a(n) of the OEIS sequence.
236
+
237
+ Raises:
238
+ ValueError: If n is negative.
239
+ KeyError: If the OEIS sequence ID is not directly implemented.
240
+ """
241
+ oeisID = _validateOEISid(oeisID)
242
+
243
+ if not isinstance(n, int) or n < 0:
244
+ raise ValueError("`n` must be non-negative integer.")
245
+
246
+ listDimensions = settingsOEIS[oeisID]['getDimensions'](n)
247
+
248
+ if n <= 1 or len(listDimensions) < 2:
249
+ foldsTotal = settingsOEIS[oeisID]['valuesKnown'].get(n, None)
250
+ if foldsTotal is not None:
251
+ return foldsTotal
252
+ else:
253
+ raise ArithmeticError(f"OEIS sequence {oeisID} is not defined at n={n}.")
254
+
255
+ return countFolds(listDimensions)
256
+
257
+ def OEIS_for_n() -> None:
258
+ """Command-line interface for oeisIDfor_n."""
259
+ import argparse
260
+
261
+ parserCLI = argparse.ArgumentParser(
262
+ description="Calculate a(n) for an OEIS sequence.",
263
+ epilog=_formatHelpText(),
264
+ formatter_class=argparse.RawDescriptionHelpFormatter
265
+ )
266
+ parserCLI.add_argument('oeisID', help="OEIS sequence identifier")
267
+ parserCLI.add_argument('n', type=int, help="Calculate a(n) for this n")
268
+
269
+ argumentsCLI = parserCLI.parse_args()
270
+
271
+ timeStart = time.perf_counter()
272
+
273
+ try:
274
+ print(oeisIDfor_n(argumentsCLI.oeisID, argumentsCLI.n), "distinct folding patterns.")
275
+ except (KeyError, ValueError, ArithmeticError) as ERRORmessage:
276
+ print(f"Error: {ERRORmessage}", file=sys.stderr)
277
+ sys.exit(1)
278
+
279
+ timeElapsed = time.perf_counter() - timeStart
280
+ print(f"Time elapsed: {timeElapsed:.3f} seconds")
281
+
282
+ def clearOEIScache() -> None:
283
+ """Delete all cached OEIS sequence files."""
284
+ if not _pathCache.exists():
285
+ print(f"Cache directory, {_pathCache}, not found - nothing to clear.")
286
+ return
287
+ else:
288
+ for oeisID in settingsOEIS:
289
+ pathFilenameCache = _pathCache / _formatFilenameCache.format(oeisID=oeisID)
290
+ pathFilenameCache.unlink(missing_ok=True)
291
+
292
+ print(f"Cache cleared from {_pathCache}")
293
+
294
+ def getOEISids() -> None:
295
+ """Print all available OEIS sequence IDs that are directly implemented."""
296
+ print(_formatHelpText())
297
+
298
+ if __name__ == "__main__":
299
+ getOEISids()
@@ -0,0 +1,132 @@
1
+ from typing import List
2
+ import numba
3
+ import numpy
4
+
5
+ @numba.jit(cache=True, nopython=True, fastmath=True)
6
+ def countFolds(listDimensions: List[int]) -> int:
7
+ """
8
+ Count the number of distinct ways to fold a map with at least two positive dimensions.
9
+
10
+ Parameters:
11
+ listDimensions: A list of integers representing the dimensions of the map. Error checking and DRY code are impermissible in the numba and jax universes. Validate the list yourself before passing here. There might be some tools for that in this package unless I have become a pyL33t coder.
12
+
13
+ Returns:
14
+ foldsTotal: The total number of distinct folds for the given map dimensions.
15
+ """
16
+ def integerSmall(value) -> numpy.uint8:
17
+ return numpy.uint8(value)
18
+
19
+ def integerLarge(value) -> numpy.uint64:
20
+ return numpy.uint64(value)
21
+
22
+ dtypeDefault = numpy.uint8
23
+ dtypeMaximum = numpy.uint16
24
+
25
+ leavesTotal = integerSmall(1)
26
+ for 个 in listDimensions:
27
+ leavesTotal = leavesTotal * integerSmall(个)
28
+ dimensionsTotal = integerSmall(len(listDimensions))
29
+
30
+ """How to build a leaf connection graph, also called a "Cartesian Product Decomposition"
31
+ or a "Dimensional Product Mapping", with sentinels:
32
+ Step 1: find the cumulative product of the map's dimensions"""
33
+ cumulativeProduct = numpy.ones(dimensionsTotal + 1, dtype=dtypeDefault)
34
+ for dimension1ndex in range(1, dimensionsTotal + 1):
35
+ cumulativeProduct[dimension1ndex] = cumulativeProduct[dimension1ndex - 1] * listDimensions[dimension1ndex - 1]
36
+
37
+ """Step 2: for each dimension, create a coordinate system """
38
+ """coordinateSystem[dimension1ndex, leaf1ndex] holds the dimension1ndex-th coordinate of leaf leaf1ndex"""
39
+ coordinateSystem = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1), dtype=dtypeDefault)
40
+ for dimension1ndex in range(1, dimensionsTotal + 1):
41
+ for leaf1ndex in range(1, leavesTotal + 1):
42
+ coordinateSystem[dimension1ndex, leaf1ndex] = ((leaf1ndex - 1) // cumulativeProduct[dimension1ndex - 1]) % listDimensions[dimension1ndex - 1] + 1
43
+
44
+ """Step 3: create a huge empty connection graph"""
45
+ connectionGraph = numpy.zeros((dimensionsTotal + 1, leavesTotal + 1, leavesTotal + 1), dtype=dtypeDefault)
46
+
47
+ """Step for... for... for...: fill the connection graph"""
48
+ for dimension1ndex in range(1, dimensionsTotal + 1):
49
+ for activeLeaf1ndex in range(1, leavesTotal + 1):
50
+ for leaf1ndexConnectee in range(1, activeLeaf1ndex + 1):
51
+ connectionGraph[dimension1ndex, activeLeaf1ndex, leaf1ndexConnectee] = (0 if leaf1ndexConnectee == 0
52
+ else ((leaf1ndexConnectee if coordinateSystem[dimension1ndex, leaf1ndexConnectee] == 1
53
+ else leaf1ndexConnectee - cumulativeProduct[dimension1ndex - 1])
54
+ if (coordinateSystem[dimension1ndex, activeLeaf1ndex] & 1) == (coordinateSystem[dimension1ndex, leaf1ndexConnectee] & 1)
55
+ else (leaf1ndexConnectee if coordinateSystem[dimension1ndex, leaf1ndexConnectee] == listDimensions[dimension1ndex-1]
56
+ or leaf1ndexConnectee + cumulativeProduct[dimension1ndex - 1] > activeLeaf1ndex
57
+ else leaf1ndexConnectee + cumulativeProduct[dimension1ndex - 1])))
58
+
59
+ """Indices of array `track` (to "track" the execution state), which is a collection of one-dimensional arrays each of length `leavesTotal + 1`."""
60
+ leafAbove = numba.literally(0)
61
+ leafBelow = numba.literally(1)
62
+ countDimensionsGapped = numba.literally(2)
63
+ gapRangeStart = numba.literally(3)
64
+ track = numpy.zeros((4, leavesTotal + 1), dtype=dtypeDefault)
65
+
66
+ gapsWhere = numpy.zeros(integerLarge(integerLarge(leavesTotal) * integerLarge(leavesTotal) + 1), dtype=dtypeMaximum)
67
+
68
+ foldsTotal = integerLarge(0)
69
+ activeLeaf1ndex = integerSmall(1)
70
+ activeGap1ndex = integerSmall(0)
71
+
72
+ while activeLeaf1ndex > 0:
73
+ if activeLeaf1ndex <= 1 or track[leafBelow, 0] == 1:
74
+ if activeLeaf1ndex > leavesTotal:
75
+ foldsTotal += leavesTotal
76
+ else:
77
+ dimensionsUnconstrained = integerSmall(0)
78
+ """Track possible gaps for activeLeaf1ndex in each section"""
79
+ gap1ndexCeiling = track[gapRangeStart, activeLeaf1ndex - 1]
80
+
81
+ """Count possible gaps for activeLeaf1ndex in each section"""
82
+ dimension1ndex = integerSmall(1)
83
+ while dimension1ndex <= dimensionsTotal:
84
+ if connectionGraph[dimension1ndex, activeLeaf1ndex, activeLeaf1ndex] == activeLeaf1ndex:
85
+ dimensionsUnconstrained += 1
86
+ else:
87
+ leaf1ndexConnectee = connectionGraph[dimension1ndex, activeLeaf1ndex, activeLeaf1ndex]
88
+ while leaf1ndexConnectee != activeLeaf1ndex:
89
+ gapsWhere[gap1ndexCeiling] = leaf1ndexConnectee
90
+ if track[countDimensionsGapped, leaf1ndexConnectee] == 0:
91
+ gap1ndexCeiling += 1
92
+ track[countDimensionsGapped, leaf1ndexConnectee] += 1
93
+ leaf1ndexConnectee = connectionGraph[dimension1ndex, activeLeaf1ndex, track[leafBelow, leaf1ndexConnectee]]
94
+ dimension1ndex += 1
95
+
96
+ """If activeLeaf1ndex is unconstrained in all sections, it can be inserted anywhere"""
97
+ if dimensionsUnconstrained == dimensionsTotal:
98
+ leaf1ndex = integerSmall(0)
99
+ while leaf1ndex < activeLeaf1ndex:
100
+ gapsWhere[gap1ndexCeiling] = leaf1ndex
101
+ gap1ndexCeiling += 1
102
+ leaf1ndex += 1
103
+
104
+ """Filter gaps that are common to all sections"""
105
+ indexMiniGap = activeGap1ndex
106
+ while indexMiniGap < gap1ndexCeiling:
107
+ gapsWhere[activeGap1ndex] = gapsWhere[indexMiniGap]
108
+ if track[countDimensionsGapped, gapsWhere[indexMiniGap]] == dimensionsTotal - dimensionsUnconstrained:
109
+ activeGap1ndex += 1
110
+ """Reset track[countDimensionsGapped] for next iteration"""
111
+ track[countDimensionsGapped, gapsWhere[indexMiniGap]] = 0
112
+ indexMiniGap += 1
113
+
114
+ """Recursive backtracking steps"""
115
+ while activeLeaf1ndex > 0 and activeGap1ndex == track[gapRangeStart, activeLeaf1ndex - 1]:
116
+ activeLeaf1ndex -= 1
117
+ track[leafBelow, track[leafAbove, activeLeaf1ndex]] = track[leafBelow, activeLeaf1ndex]
118
+ track[leafAbove, track[leafBelow, activeLeaf1ndex]] = track[leafAbove, activeLeaf1ndex]
119
+
120
+ """Place leaf in valid position"""
121
+ if activeLeaf1ndex > 0:
122
+ activeGap1ndex -= 1
123
+ track[leafAbove, activeLeaf1ndex] = gapsWhere[activeGap1ndex]
124
+ track[leafBelow, activeLeaf1ndex] = track[leafBelow, track[leafAbove, activeLeaf1ndex]]
125
+ track[leafBelow, track[leafAbove, activeLeaf1ndex]] = activeLeaf1ndex
126
+ track[leafAbove, track[leafBelow, activeLeaf1ndex]] = activeLeaf1ndex
127
+ """Save current gap index"""
128
+ track[gapRangeStart, activeLeaf1ndex] = activeGap1ndex
129
+ """Move to next leaf"""
130
+ activeLeaf1ndex += 1
131
+
132
+ return int(foldsTotal)
@@ -0,0 +1,120 @@
1
+ """
2
+ Ported from the Java version by Sean A. Irvine:
3
+ https://github.com/archmageirvine/joeis/blob/80e3e844b11f149704acbab520bc3a3a25ac34ff/src/irvine/oeis/a001/A001415.java
4
+
5
+ Citation: mapFolding/citations/jOEIS.bibtex
6
+ """
7
+ def foldings(p: list[int], res: int = 0, mod: int = 0) -> int:
8
+ """
9
+ Compute the total number of foldings for a map with dimensions specified in p.
10
+
11
+ Parameters:
12
+ p: List of integers representing the dimensions of the map.
13
+ res: Residue for modulo operation (integer).
14
+ mod: Modulus for modulo operation (integer).
15
+
16
+ Returns:
17
+ total_count: The total number of foldings (integer).
18
+ """
19
+ n = 1 # Total number of leaves
20
+ d = len(p) # Number of dimensions
21
+ for dimension in p:
22
+ n *= dimension
23
+
24
+ # Initialize arrays/lists
25
+ A = [0] * (n + 1) # Leaf above leaf m
26
+ B = [0] * (n + 1) # Leaf below leaf m
27
+ count = [0] * (n + 1) # Counts for potential gaps
28
+ gapter = [0] * (n + 1) # Indices for gap stack per leaf
29
+ gap = [0] * (n * n + 1) # Stack of potential gaps
30
+
31
+ # Compute arrays P, C, D as per the algorithm
32
+ P = [1] * (d + 1)
33
+ for i in range(1, d + 1):
34
+ P[i] = P[i - 1] * p[i - 1]
35
+
36
+ # C[i][m] holds the i-th coordinate of leaf m
37
+ C = [[0] * (n + 1) for _ in range(d + 1)]
38
+ for i in range(1, d + 1):
39
+ for m in range(1, n + 1):
40
+ C[i][m] = ((m - 1) // P[i - 1]) - ((m - 1) // P[i]) * p[i - 1] + 1
41
+
42
+ # D[i][l][m] computes the leaf connected to m in section i when inserting l
43
+ D = [[[0] * (n + 1) for _ in range(n + 1)] for _ in range(d + 1)]
44
+ for i in range(1, d + 1):
45
+ for l in range(1, n + 1):
46
+ for m in range(1, l + 1):
47
+ delta = C[i][l] - C[i][m]
48
+ if delta % 2 == 0:
49
+ # If delta is even
50
+ if C[i][m] == 1:
51
+ D[i][l][m] = m
52
+ else:
53
+ D[i][l][m] = m - P[i - 1]
54
+ else:
55
+ # If delta is odd
56
+ if C[i][m] == p[i - 1] or m + P[i - 1] > l:
57
+ D[i][l][m] = m
58
+ else:
59
+ D[i][l][m] = m + P[i - 1]
60
+
61
+ # Initialize variables for backtracking
62
+ total_count = 0 # Total number of foldings
63
+ g = 0 # Gap index
64
+ l = 1 # Current leaf
65
+
66
+ # Start backtracking loop
67
+ while l > 0:
68
+ # If we have processed all leaves, increment total count
69
+ if l > n:
70
+ total_count += 1
71
+ else:
72
+ dd = 0 # Number of sections where leaf l is unconstrained
73
+ gg = g # Temporary gap index
74
+ g = gapter[l - 1] # Reset gap index for current leaf
75
+
76
+ # Count possible gaps for leaf l in each section
77
+ for i in range(1, d + 1):
78
+ if D[i][l][l] == l:
79
+ dd += 1
80
+ else:
81
+ m = D[i][l][l]
82
+ while m != l:
83
+ if mod == 0 or l != mod or m % mod == res:
84
+ gap[gg] = m
85
+ if count[m] == 0:
86
+ gg += 1
87
+ count[m] += 1
88
+ m = D[i][l][B[m]]
89
+
90
+ # If leaf l is unconstrained in all sections, it can be inserted anywhere
91
+ if dd == d:
92
+ for m in range(l):
93
+ gap[gg] = m
94
+ gg += 1
95
+
96
+ # Filter gaps that are common to all sections
97
+ for j in range(g, gg):
98
+ gap[g] = gap[j]
99
+ if count[gap[j]] == d - dd:
100
+ g += 1
101
+ count[gap[j]] = 0 # Reset count for next iteration
102
+
103
+ # Recursive backtracking steps
104
+ while l > 0 and g == gapter[l - 1]:
105
+ # No more gaps to try, backtrack to previous leaf
106
+ l -= 1
107
+ B[A[l]] = B[l]
108
+ A[B[l]] = A[l]
109
+
110
+ if l > 0:
111
+ # Try next gap for leaf l
112
+ g -= 1
113
+ A[l] = gap[g]
114
+ B[l] = B[A[l]]
115
+ B[A[l]] = l
116
+ A[B[l]] = l
117
+ gapter[l] = g # Save current gap index
118
+ l += 1 # Move to next leaf
119
+
120
+ return total_count