Skip to content

Commit e334f85

Browse files
committed
Improve OEIS cache validation with conditional HTTP requests. The universal slowdown seems to be gone.
1 parent 071b34e commit e334f85

File tree

6 files changed

+99
-92
lines changed

6 files changed

+99
-92
lines changed

.vscode/code-spell-dictionary-workspace.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ Unroller
373373
unsignedinteger
374374
untenably
375375
urldate
376+
usegmt
376377
Valjean
377378
valmap
378379
vararg

mapFolding/_e/easyRun/eliminateFolds.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
# ruff: noqa
22
# pyright: basic
3-
from functools import partial
4-
from itertools import filterfalse
53
from mapFolding import ansiColorReset, ansiColors
6-
from mapFolding._e import PermutationSpace
74
from mapFolding._e.basecamp import eliminateFolds
85
from mapFolding._e.dataBaskets import EliminationState
9-
from mapFolding._e.filters import between吗, extractPinnedLeaves
10-
from mapFolding._e.pin2上nDimensions import (
11-
pinLeavesDimensions0零一, pinLeavesDimension一, pinLeavesDimension二, pinLeavesDimension首二, pinPilesAtEnds, pinPile零Ante首零)
6+
from mapFolding._e.pin2上nDimensions import pinLeavesDimensions0零一
127
from mapFolding.oeis import dictionaryOEISMapFolding
138
from os import PathLike
14-
from pathlib import Path, PurePath
15-
from tqdm import tqdm
16-
import csv
9+
from pathlib import PurePath
1710
import sys
1811
import time
1912

mapFolding/_e/easyRun/pinning.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ def prodOfDOTvalues(listPileRangeOfLeaves: Iterable[PileRangeOfLeaves]) -> int:
4747
print(f"{len(state.listPermutationSpace)=}")
4848

4949
elif printThis:
50+
state: EliminationState = pinLeavesDimensions0零一(state)
5051
state: EliminationState = pinLeavesDimension首二(state)
5152
state: EliminationState = pinPile零Ante首零(state)
52-
state: EliminationState = pinLeavesDimensions0零一(state)
5353
print(state.sumsOfProductsOfDimensionsNearest首)
5454
pprint(dictionaryPileRanges := getDictionaryPileRanges(state), width=200)
5555
pprint(dictionaryLeafDomains := getDictionaryLeafDomains(state))

mapFolding/basecamp.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,3 @@ def countFolds(listDimensions: Sequence[int] | None = None
195195

196196
return foldsTotal
197197

198-
# SEMIOTICS `NOTcountingFolds`: improve identifier.
199-
200-
# TODO A long time ago, I had an explicit rule written in "oeis.py" that the module contained only OEIS stuff and ALL OEIS stuff.
201-
# This function is fundamentally an OEIS function, but I have been trying to treat it the same as `countingFolds`. That mismatch
202-
# is a major reason for the many problems I've had with semiotics and flow design. `oeisIDfor_n` _might_ be the correct identifier
203-
# for this function. I created `oeisIDfor_n` a very very very long time ago, and I think my brain might have locked into it being
204-
# a frontend for `countFolds`. I made `oeisIDfor_n` before I had a need for the `flow` parameter. Since creating the `flow`
205-
# parameter, I have been trying to figure out how to put it into `oeisIDfor_n`. For a long time, `oeisIDfor_n` would call numba
206-
# theorem2 because it was the fastest, but I made numba an optional dependency. All of these seemingly unrelated issues underscore
207-
# the importance of the semiotics-first paradigm (for me).

mapFolding/oeis.py

Lines changed: 93 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,7 @@
11
# ruff: noqa: PLC0415 E701
2-
3-
"""
4-
Mathematical validation and discovery through OEIS integration.
5-
6-
(AI generated docstring)
7-
8-
Complementing the unified computational interface, this module extends the map
9-
folding ecosystem into the broader mathematical community through comprehensive
10-
integration with the Online Encyclopedia of Integer Sequences (OEIS). This bridge
11-
enables validation of computational results against established mathematical
12-
knowledge while supporting the discovery of new sequence values through the
13-
sophisticated computational assembly line.
14-
15-
The integration provides multiple pathways for mathematical verification: direct
16-
computation of OEIS sequences using the complete algorithmic implementation,
17-
cached access to published sequence data for rapid validation, and research
18-
support for extending known sequences through new computational discoveries.
19-
The module handles sequence families ranging from simple strip folding to
20-
complex multi-dimensional hypercube problems.
21-
22-
Through intelligent caching and optimized lookup mechanisms, this module ensures
23-
that the computational power developed through the foundational layers can contribute
24-
meaningfully to mathematical research. Whether validating results, avoiding
25-
redundant computation, or extending mathematical knowledge, this integration
26-
completes the journey from configuration foundation to mathematical discovery.
27-
"""
28-
292
from datetime import datetime, timedelta, UTC
3+
from email.utils import format_datetime
4+
from functools import cache
305
from hunterMakesPy.filesystemToolkit import writeStringToHere
316
from itertools import chain
327
from mapFolding import MetadataOEISid, MetadataOEISidMapFolding, packageSettings
@@ -37,11 +12,10 @@
3712
from os import PathLike
3813
from pathlib import Path, PurePath
3914
from typing import Final, Literal
40-
from urllib.error import HTTPError, URLError
41-
from urllib.request import urlopen
4215
import argparse
4316
import sys
4417
import time
18+
import urllib3
4519
import warnings
4620

4721
def _standardizeOEISid(oeisID: str) -> str:
@@ -149,22 +123,56 @@ def _getOEISofficial(pathFilenameCache: Path, url: str) -> None | str:
149123

150124
if not tryCache:
151125
if not url.startswith(("http:", "https:")):
152-
message = "URL must start with 'http:' or 'https:'"
126+
message: str = "URL must start with 'http:' or 'https:'"
153127
raise ValueError(message)
154128

129+
cachedInformation: str | None = None
130+
cacheIsReadable: bool = False
131+
if pathFilenameCache.exists():
132+
try:
133+
cachedInformation = pathFilenameCache.read_text(encoding="utf-8")
134+
cacheIsReadable = True
135+
except OSError:
136+
cacheIsReadable = False
137+
138+
headers: dict[str, str] | None = None
139+
if cacheIsReadable:
140+
cacheDatetime: datetime = datetime.fromtimestamp(pathFilenameCache.stat().st_mtime, tz=UTC)
141+
headers = {"If-Modified-Since": format_datetime(cacheDatetime, usegmt=True)}
142+
143+
httpPoolManager = urllib3.PoolManager(retries=False)
155144
try:
156-
with urlopen(url) as response: # noqa: S310
157-
oeisInformationRaw = response.read().decode('utf-8')
158-
oeisInformation = str(oeisInformationRaw)
159-
writeStringToHere(oeisInformation, pathFilenameCache)
160-
except (HTTPError, URLError):
161-
oeisInformation = pathFilenameCache.read_text(encoding="utf-8")
145+
response = httpPoolManager.request(
146+
"GET"
147+
, url
148+
, headers=headers
149+
, preload_content=True
150+
, decode_content=True
151+
)
152+
if response.status == 304:
153+
oeisInformation = cachedInformation
154+
if cacheIsReadable:
155+
pathFilenameCache.touch()
156+
elif response.status == 200:
157+
oeisInformation = response.data.decode("utf-8")
158+
if (cachedInformation is not None) and (oeisInformation == cachedInformation):
159+
pathFilenameCache.touch()
160+
else:
161+
writeStringToHere(oeisInformation, pathFilenameCache)
162+
else:
163+
oeisInformation = cachedInformation
164+
except urllib3.exceptions.HTTPError:
165+
oeisInformation = cachedInformation
166+
finally:
167+
httpPoolManager.clear()
162168

163169
if not oeisInformation:
164-
warnings.warn(f"Failed to retrieve OEIS sequence information for {pathFilenameCache.stem}.", stacklevel=2)
170+
message: str = f"Failed to retrieve OEIS sequence information for {pathFilenameCache.stem}."
171+
warnings.warn(message, stacklevel=2)
165172

166173
return oeisInformation
167174

175+
@cache
168176
def getOEISidValues(oeisID: str) -> dict[int, int]:
169177
"""Retrieve known sequence values for a specified OEIS sequence.
170178
@@ -202,6 +210,7 @@ def getOEISidValues(oeisID: str) -> dict[int, int]:
202210
return _parseBFileOEIS(oeisInformation)
203211
return {-1: -1}
204212

213+
@cache
205214
def getOEISidInformation(oeisID: str) -> tuple[str, int]:
206215
"""Retrieve the description and offset metadata for an OEIS sequence.
207216
@@ -258,6 +267,8 @@ def getOEISidInformation(oeisID: str) -> tuple[str, int]:
258267
description: str = ' '.join(listDescriptionDeconstructed)
259268
return description, offset
260269

270+
#======== Dictionaries of OEIS sequence metadata ==============================================================
271+
261272
def _makeDictionaryOEISMapFolding() -> dict[str, MetadataOEISidMapFolding]:
262273
"""Construct the comprehensive settings dictionary for all implemented OEIS sequences.
263274
@@ -294,22 +305,27 @@ def _makeDictionaryOEISMapFolding() -> dict[str, MetadataOEISidMapFolding]:
294305
)
295306
return dictionaryOEIS
296307

308+
def _makeDictionaryOEIS() -> dict[str, MetadataOEISid]:
309+
dictionary: dict[str, MetadataOEISid] = {}
310+
for oeisID in packageSettings.OEISidManuallySet:
311+
valuesKnownSherpa: dict[int, int] = getOEISidValues(oeisID)
312+
descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
313+
dictionary[oeisID] = MetadataOEISid(
314+
description=descriptionSherpa,
315+
offset=offsetSherpa,
316+
valuesKnown=valuesKnownSherpa,
317+
valueUnknown=max(valuesKnownSherpa.keys(), default=0) + 1,
318+
)
319+
return dictionary
320+
297321
dictionaryOEISMapFolding: dict[str, MetadataOEISidMapFolding] = _makeDictionaryOEISMapFolding()
298322
"""Metadata for each MapFolding OEIS ID."""
299323

300-
def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
301-
"""Make a `mapShape` to known `foldsTotal` dictionary.
302-
303-
Returns
304-
-------
305-
dictionaryFoldsTotalKnown : dict[tuple[int, ...], int]
306-
A dictionary where keys are tuples representing map shapes and values are the total number
307-
of distinct folding patterns for those shapes.
324+
dictionaryOEIS: dict[str, MetadataOEISid] = _makeDictionaryOEIS()
308325

309-
"""
310-
return dict(chain.from_iterable(zip(map(oeisIDmetadata['getMapShape'], oeisIDmetadata['valuesKnown'].keys())
311-
, oeisIDmetadata['valuesKnown'].values(), strict=True) for oeisID, oeisIDmetadata in dictionaryOEISMapFolding.items() if oeisID != 'A007822'))
326+
#======== getFoldsTotalKnown ==============================================================
312327

328+
@cache
313329
def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
314330
"""Retrieve the known total number of distinct folding patterns for a given map shape.
315331
@@ -332,6 +348,21 @@ def getFoldsTotalKnown(mapShape: tuple[int, ...]) -> int:
332348
lookupFoldsTotal: dict[tuple[int, ...], int] = makeDictionaryFoldsTotalKnown()
333349
return lookupFoldsTotal.get(tuple(mapShape), 0)
334350

351+
def makeDictionaryFoldsTotalKnown() -> dict[tuple[int, ...], int]:
352+
"""Make a `mapShape` to known `foldsTotal` dictionary.
353+
354+
Returns
355+
-------
356+
dictionaryFoldsTotalKnown : dict[tuple[int, ...], int]
357+
A dictionary where keys are tuples representing map shapes and values are the total number
358+
of distinct folding patterns for those shapes.
359+
360+
"""
361+
return dict(chain.from_iterable(zip(map(oeisIDmetadata['getMapShape'], oeisIDmetadata['valuesKnown'].keys())
362+
, oeisIDmetadata['valuesKnown'].values(), strict=True) for oeisID, oeisIDmetadata in dictionaryOEISMapFolding.items() if oeisID != 'A007822'))
363+
364+
#======== OEIS for n ==============================================================
365+
335366
def _formatHelpText() -> str:
336367
"""Format comprehensive help text for both command-line and interactive use.
337368
@@ -483,29 +514,17 @@ def getOEISids() -> None:
483514
"""
484515
print(_formatHelpText()) # noqa: T201
485516

486-
def _makeDictionaryOEIS() -> dict[str, MetadataOEISid]:
487-
dictionary: dict[str, MetadataOEISid] = {}
488-
for oeisID in packageSettings.OEISidManuallySet:
489-
valuesKnownSherpa: dict[int, int] = getOEISidValues(oeisID)
490-
descriptionSherpa, offsetSherpa = getOEISidInformation(oeisID)
491-
dictionary[oeisID] = MetadataOEISid(
492-
description=descriptionSherpa,
493-
offset=offsetSherpa,
494-
valuesKnown=valuesKnownSherpa,
495-
valueUnknown=max(valuesKnownSherpa.keys(), default=0) + 1,
496-
)
497-
return dictionary
498-
499-
dictionaryOEIS: dict[str, MetadataOEISid] = _makeDictionaryOEIS()
500-
501-
if __name__ == "__main__":
502-
getOEISids()
503-
504-
505-
def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None
506-
, pathLikeWriteFoldsTotal: PathLike[str] | PurePath | None = None
507-
, CPUlimit: bool | float | int | None = None # noqa: FBT001
508-
) -> int:
517+
# SEMIOTICS `NOTcountingFolds`: improve identifier.
518+
519+
# TODO A long time ago, I had an explicit rule written in "oeis.py" that the module contained only OEIS stuff and ALL OEIS stuff.
520+
# This function is fundamentally an OEIS function, but I have been trying to treat it the same as `countingFolds`. That mismatch
521+
# is a major reason for the many problems I've had with semiotics and flow design. `oeisIDfor_n` _might_ be the correct identifier
522+
# for this function. I created `oeisIDfor_n` a very very very long time ago, and I think my brain might have locked into it being
523+
# a frontend for `countFolds`. I made `oeisIDfor_n` before I had a need for the `flow` parameter. Since creating the `flow`
524+
# parameter, I have been trying to figure out how to put it into `oeisIDfor_n`. For a long time, `oeisIDfor_n` would call numba
525+
# theorem2 because it was the fastest, but I made numba an optional dependency. All of these seemingly unrelated issues underscore
526+
# the importance of the semiotics-first paradigm (for me).
527+
def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None, pathLikeWriteFoldsTotal: PathLike[str] | PurePath | None = None, CPUlimit: bool | float | int | None = None) -> int: # noqa: FBT001
509528
"""You can compute the n-th term of specified OEIS sequences using specialized algorithms.
510529
511530
(AI generated docstring)
@@ -748,3 +767,6 @@ def NOTcountingFolds(oeisID: str, oeis_n: int, flow: str | None = None
748767

749768
return countTotal
750769

770+
if __name__ == "__main__":
771+
getOEISids()
772+

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "mapFolding"
3-
version = "0.18.0"
3+
version = "0.18.1"
44
description = "Map folding, meanders, stamp folding, semi-meanders. Experiment with algorithm transformations, and analyze computational states."
55
readme = "README.md"
66
requires-python = ">=3.12"
@@ -55,6 +55,7 @@ dependencies = [
5555
"numpy",
5656
"platformdirs",
5757
"tqdm",
58+
"urllib3",
5859
]
5960
optional-dependencies = { development = [
6061
"gmpy2-stubs",

0 commit comments

Comments
 (0)