tnfr 4.5.2__py3-none-any.whl → 8.5.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.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/flatten.pyi
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .tokens import THOL, Token
|
|
7
|
+
|
|
8
|
+
__all__: list[str]
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str) -> Any: ...
|
|
11
|
+
|
|
12
|
+
class THOLEvaluator(Iterator[Token | object]):
|
|
13
|
+
def __init__(self, item: THOL, *, max_materialize: int | None = ...) -> None: ...
|
|
14
|
+
def __iter__(self) -> THOLEvaluator: ...
|
|
15
|
+
def __next__(self) -> Token | object: ...
|
|
16
|
+
|
|
17
|
+
def parse_program_tokens(
|
|
18
|
+
obj: Iterable[Any] | Sequence[Any] | Any,
|
|
19
|
+
*,
|
|
20
|
+
max_materialize: int | None = ...,
|
|
21
|
+
) -> list[Token]: ...
|
tnfr/gamma.py
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
"""Gamma registry."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import math
|
|
6
|
-
import logging
|
|
4
|
+
|
|
7
5
|
import hashlib
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
8
|
from collections.abc import Mapping
|
|
9
9
|
from functools import lru_cache
|
|
10
10
|
from types import MappingProxyType
|
|
11
|
+
from typing import Any, Callable, NamedTuple
|
|
11
12
|
|
|
12
|
-
from .
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
from .cache import edge_version_cache, node_set_checksum
|
|
16
|
-
from .json_utils import json_dumps
|
|
17
|
-
from .logging_utils import get_logger
|
|
13
|
+
from .alias import get_theta_attr
|
|
14
|
+
from .constants import DEFAULTS
|
|
15
|
+
from .utils import json_dumps
|
|
18
16
|
from .metrics.trig_cache import get_trig_cache
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
from .types import GammaSpec, NodeId, TNFRGraph
|
|
18
|
+
from .utils import (
|
|
19
|
+
edge_version_cache,
|
|
20
|
+
get_graph_mapping,
|
|
21
|
+
get_logger,
|
|
22
|
+
node_set_checksum,
|
|
23
|
+
)
|
|
22
24
|
|
|
23
25
|
logger = get_logger(__name__)
|
|
24
26
|
|
|
@@ -44,9 +46,8 @@ def _default_gamma_spec() -> tuple[bytes, str]:
|
|
|
44
46
|
return dumped, hash_
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
def _ensure_kuramoto_cache(G, t) -> None:
|
|
48
|
-
"""Cache ``(R, ψ)`` for the current step ``t`` using
|
|
49
|
-
``edge_version_cache``."""
|
|
49
|
+
def _ensure_kuramoto_cache(G: TNFRGraph, t: float | int) -> None:
|
|
50
|
+
"""Cache ``(R, ψ)`` for the current step ``t`` using ``edge_version_cache``."""
|
|
50
51
|
checksum = G.graph.get("_dnfr_nodes_checksum")
|
|
51
52
|
if checksum is None:
|
|
52
53
|
# reuse checksum from cached_nodes_and_A when available
|
|
@@ -63,7 +64,7 @@ def _ensure_kuramoto_cache(G, t) -> None:
|
|
|
63
64
|
G.graph["_kuramoto_cache"] = entry
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
def kuramoto_R_psi(G) -> tuple[float, float]:
|
|
67
|
+
def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]:
|
|
67
68
|
"""Return ``(R, ψ)`` for Kuramoto order using θ from all nodes."""
|
|
68
69
|
max_steps = int(G.graph.get("KURAMOTO_CACHE_STEPS", 1))
|
|
69
70
|
trig = get_trig_cache(G, cache_size=max_steps)
|
|
@@ -78,7 +79,9 @@ def kuramoto_R_psi(G) -> tuple[float, float]:
|
|
|
78
79
|
return R, psi
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
def _kuramoto_common(
|
|
82
|
+
def _kuramoto_common(
|
|
83
|
+
G: TNFRGraph, node: NodeId, _cfg: GammaSpec
|
|
84
|
+
) -> tuple[float, float, float]:
|
|
82
85
|
"""Return ``(θ_i, R, ψ)`` for Kuramoto-based Γ functions.
|
|
83
86
|
|
|
84
87
|
Reads cached global order ``R`` and mean phase ``ψ`` and obtains node
|
|
@@ -88,11 +91,12 @@ def _kuramoto_common(G, node, _cfg):
|
|
|
88
91
|
cache = G.graph.get("_kuramoto_cache", {})
|
|
89
92
|
R = float(cache.get("R", 0.0))
|
|
90
93
|
psi = float(cache.get("psi", 0.0))
|
|
91
|
-
|
|
94
|
+
th_val = get_theta_attr(G.nodes[node], 0.0)
|
|
95
|
+
th_i = float(th_val if th_val is not None else 0.0)
|
|
92
96
|
return th_i, R, psi
|
|
93
97
|
|
|
94
98
|
|
|
95
|
-
def _read_gamma_raw(G) ->
|
|
99
|
+
def _read_gamma_raw(G: TNFRGraph) -> GammaSpec | None:
|
|
96
100
|
"""Return raw Γ specification from ``G.graph['GAMMA']``.
|
|
97
101
|
|
|
98
102
|
The returned value is the direct contents of ``G.graph['GAMMA']`` when
|
|
@@ -104,11 +108,13 @@ def _read_gamma_raw(G) -> Mapping[str, Any] | None:
|
|
|
104
108
|
if raw is None or isinstance(raw, Mapping):
|
|
105
109
|
return raw
|
|
106
110
|
return get_graph_mapping(
|
|
107
|
-
G,
|
|
111
|
+
G,
|
|
112
|
+
"GAMMA",
|
|
113
|
+
"G.graph['GAMMA'] is not a mapping; using {'type': 'none'}",
|
|
108
114
|
)
|
|
109
115
|
|
|
110
116
|
|
|
111
|
-
def _get_gamma_spec(G) ->
|
|
117
|
+
def _get_gamma_spec(G: TNFRGraph) -> GammaSpec:
|
|
112
118
|
"""Return validated Γ specification caching results.
|
|
113
119
|
|
|
114
120
|
The raw value from ``G.graph['GAMMA']`` is cached together with the
|
|
@@ -122,7 +128,7 @@ def _get_gamma_spec(G) -> Mapping[str, Any]:
|
|
|
122
128
|
cached_spec = G.graph.get("_gamma_spec")
|
|
123
129
|
cached_hash = G.graph.get("_gamma_spec_hash")
|
|
124
130
|
|
|
125
|
-
def _hash_mapping(mapping:
|
|
131
|
+
def _hash_mapping(mapping: GammaSpec) -> str:
|
|
126
132
|
dumped = json_dumps(mapping, sort_keys=True, to_bytes=True)
|
|
127
133
|
return hashlib.blake2b(dumped, digest_size=16).hexdigest()
|
|
128
134
|
|
|
@@ -165,9 +171,7 @@ def _get_gamma_spec(G) -> Mapping[str, Any]:
|
|
|
165
171
|
# -----------------
|
|
166
172
|
|
|
167
173
|
|
|
168
|
-
def _gamma_params(
|
|
169
|
-
cfg: Mapping[str, Any], **defaults: float
|
|
170
|
-
) -> tuple[float, ...]:
|
|
174
|
+
def _gamma_params(cfg: GammaSpec, **defaults: float) -> tuple[float, ...]:
|
|
171
175
|
"""Return normalized Γ parameters from ``cfg``.
|
|
172
176
|
|
|
173
177
|
Parameters are retrieved from ``cfg`` using the keys in ``defaults`` and
|
|
@@ -179,28 +183,28 @@ def _gamma_params(
|
|
|
179
183
|
>>> beta, R0 = _gamma_params(cfg, beta=0.0, R0=0.0)
|
|
180
184
|
"""
|
|
181
185
|
|
|
182
|
-
return tuple(
|
|
183
|
-
float(cfg.get(name, default)) for name, default in defaults.items()
|
|
184
|
-
)
|
|
186
|
+
return tuple(float(cfg.get(name, default)) for name, default in defaults.items())
|
|
185
187
|
|
|
186
188
|
|
|
187
189
|
# -----------------
|
|
188
|
-
# Γi(R)
|
|
190
|
+
# Canonical Γi(R)
|
|
189
191
|
# -----------------
|
|
190
192
|
|
|
191
193
|
|
|
192
|
-
def gamma_none(G, node, t, cfg:
|
|
194
|
+
def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
|
|
195
|
+
"""Return ``0.0`` to disable Γ forcing for the given node."""
|
|
196
|
+
|
|
193
197
|
return 0.0
|
|
194
198
|
|
|
195
199
|
|
|
196
200
|
def _gamma_kuramoto(
|
|
197
|
-
G,
|
|
198
|
-
node,
|
|
199
|
-
cfg:
|
|
201
|
+
G: TNFRGraph,
|
|
202
|
+
node: NodeId,
|
|
203
|
+
cfg: GammaSpec,
|
|
200
204
|
builder: Callable[..., float],
|
|
201
205
|
**defaults: float,
|
|
202
206
|
) -> float:
|
|
203
|
-
"""
|
|
207
|
+
"""Construct a Kuramoto-based Γ function.
|
|
204
208
|
|
|
205
209
|
``builder`` receives ``(θ_i, R, ψ, *params)`` where ``params`` are
|
|
206
210
|
extracted from ``cfg`` according to ``defaults``.
|
|
@@ -220,11 +224,15 @@ def _builder_bandpass(th_i: float, R: float, psi: float, beta: float) -> float:
|
|
|
220
224
|
return beta * R * (1.0 - R) * sgn
|
|
221
225
|
|
|
222
226
|
|
|
223
|
-
def _builder_tanh(
|
|
227
|
+
def _builder_tanh(
|
|
228
|
+
th_i: float, R: float, psi: float, beta: float, k: float, R0: float
|
|
229
|
+
) -> float:
|
|
224
230
|
return beta * math.tanh(k * (R - R0)) * math.cos(th_i - psi)
|
|
225
231
|
|
|
226
232
|
|
|
227
|
-
def gamma_kuramoto_linear(
|
|
233
|
+
def gamma_kuramoto_linear(
|
|
234
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
235
|
+
) -> float:
|
|
228
236
|
"""Linear Kuramoto coupling for Γi(R).
|
|
229
237
|
|
|
230
238
|
Formula: Γ = β · (R - R0) · cos(θ_i - ψ)
|
|
@@ -239,13 +247,17 @@ def gamma_kuramoto_linear(G, node, t, cfg: dict[str, Any]) -> float:
|
|
|
239
247
|
return _gamma_kuramoto(G, node, cfg, _builder_linear, beta=0.0, R0=0.0)
|
|
240
248
|
|
|
241
249
|
|
|
242
|
-
def gamma_kuramoto_bandpass(
|
|
243
|
-
|
|
250
|
+
def gamma_kuramoto_bandpass(
|
|
251
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
252
|
+
) -> float:
|
|
253
|
+
"""Compute Γ = β · R(1-R) · sign(cos(θ_i - ψ))."""
|
|
244
254
|
|
|
245
255
|
return _gamma_kuramoto(G, node, cfg, _builder_bandpass, beta=0.0)
|
|
246
256
|
|
|
247
257
|
|
|
248
|
-
def gamma_kuramoto_tanh(
|
|
258
|
+
def gamma_kuramoto_tanh(
|
|
259
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
260
|
+
) -> float:
|
|
249
261
|
"""Saturating tanh coupling for Γi(R).
|
|
250
262
|
|
|
251
263
|
Formula: Γ = β · tanh(k·(R - R0)) · cos(θ_i - ψ)
|
|
@@ -257,7 +269,7 @@ def gamma_kuramoto_tanh(G, node, t, cfg: dict[str, Any]) -> float:
|
|
|
257
269
|
return _gamma_kuramoto(G, node, cfg, _builder_tanh, beta=0.0, k=1.0, R0=0.0)
|
|
258
270
|
|
|
259
271
|
|
|
260
|
-
def gamma_harmonic(G, node, t, cfg:
|
|
272
|
+
def gamma_harmonic(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float:
|
|
261
273
|
"""Harmonic forcing aligned with the global phase field.
|
|
262
274
|
|
|
263
275
|
Formula: Γ = β · sin(ω·t + φ) · cos(θ_i - ψ)
|
|
@@ -271,13 +283,15 @@ def gamma_harmonic(G, node, t, cfg: dict[str, Any]) -> float:
|
|
|
271
283
|
|
|
272
284
|
|
|
273
285
|
class GammaEntry(NamedTuple):
|
|
274
|
-
|
|
286
|
+
"""Lookup entry linking Γ evaluators with their preconditions."""
|
|
287
|
+
|
|
288
|
+
fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
|
|
275
289
|
needs_kuramoto: bool
|
|
276
290
|
|
|
277
291
|
|
|
278
|
-
# ``GAMMA_REGISTRY``
|
|
279
|
-
# ``
|
|
280
|
-
#
|
|
292
|
+
# ``GAMMA_REGISTRY`` associates each coupling name with a ``GammaEntry`` where
|
|
293
|
+
# ``fn`` is the evaluation function and ``needs_kuramoto`` indicates whether
|
|
294
|
+
# the global phase order must be precomputed.
|
|
281
295
|
GAMMA_REGISTRY: dict[str, GammaEntry] = {
|
|
282
296
|
"none": GammaEntry(gamma_none, False),
|
|
283
297
|
"kuramoto_linear": GammaEntry(gamma_kuramoto_linear, True),
|
|
@@ -288,20 +302,19 @@ GAMMA_REGISTRY: dict[str, GammaEntry] = {
|
|
|
288
302
|
|
|
289
303
|
|
|
290
304
|
def eval_gamma(
|
|
291
|
-
G,
|
|
292
|
-
node,
|
|
293
|
-
t,
|
|
305
|
+
G: TNFRGraph,
|
|
306
|
+
node: NodeId,
|
|
307
|
+
t: float | int,
|
|
294
308
|
*,
|
|
295
309
|
strict: bool = False,
|
|
296
310
|
log_level: int | None = None,
|
|
297
311
|
) -> float:
|
|
298
|
-
"""Evaluate Γi for ``node``
|
|
299
|
-
specification.
|
|
312
|
+
"""Evaluate Γi for ``node`` using ``G.graph['GAMMA']`` specification.
|
|
300
313
|
|
|
301
314
|
If ``strict`` is ``True`` exceptions raised during evaluation are
|
|
302
315
|
propagated instead of returning ``0.0``. Likewise, if the specified
|
|
303
|
-
Γ type is not registered a warning is emitted (
|
|
304
|
-
|
|
316
|
+
Γ type is not registered a warning is emitted (or ``ValueError`` in
|
|
317
|
+
strict mode) and ``gamma_none`` is used.
|
|
305
318
|
|
|
306
319
|
``log_level`` controls the logging level for captured errors when
|
|
307
320
|
``strict`` is ``False``. If omitted, ``logging.ERROR`` is used in
|
|
@@ -311,7 +324,7 @@ def eval_gamma(
|
|
|
311
324
|
spec_type = spec.get("type", "none")
|
|
312
325
|
reg_entry = GAMMA_REGISTRY.get(spec_type)
|
|
313
326
|
if reg_entry is None:
|
|
314
|
-
msg = f"
|
|
327
|
+
msg = f"Unknown GAMMA type: {spec_type}"
|
|
315
328
|
if strict:
|
|
316
329
|
raise ValueError(msg)
|
|
317
330
|
logger.warning(msg)
|
|
@@ -330,7 +343,7 @@ def eval_gamma(
|
|
|
330
343
|
)
|
|
331
344
|
logger.log(
|
|
332
345
|
level,
|
|
333
|
-
"
|
|
346
|
+
"Failed to evaluate Γi for node %s at t=%s: %s: %s",
|
|
334
347
|
node,
|
|
335
348
|
t,
|
|
336
349
|
exc.__class__.__name__,
|
tnfr/gamma.pyi
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable, NamedTuple
|
|
4
|
+
|
|
5
|
+
from .types import GammaSpec, NodeId, TNFRGraph
|
|
6
|
+
|
|
7
|
+
__all__: tuple[str, ...]
|
|
8
|
+
|
|
9
|
+
class GammaEntry(NamedTuple):
|
|
10
|
+
fn: Callable[[TNFRGraph, NodeId, float | int, GammaSpec], float]
|
|
11
|
+
needs_kuramoto: bool
|
|
12
|
+
|
|
13
|
+
GAMMA_REGISTRY: dict[str, GammaEntry]
|
|
14
|
+
|
|
15
|
+
def kuramoto_R_psi(G: TNFRGraph) -> tuple[float, float]: ...
|
|
16
|
+
def gamma_none(G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec) -> float: ...
|
|
17
|
+
def gamma_kuramoto_linear(
|
|
18
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
19
|
+
) -> float: ...
|
|
20
|
+
def gamma_kuramoto_bandpass(
|
|
21
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
22
|
+
) -> float: ...
|
|
23
|
+
def gamma_kuramoto_tanh(
|
|
24
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
25
|
+
) -> float: ...
|
|
26
|
+
def gamma_harmonic(
|
|
27
|
+
G: TNFRGraph, node: NodeId, t: float | int, cfg: GammaSpec
|
|
28
|
+
) -> float: ...
|
|
29
|
+
def eval_gamma(
|
|
30
|
+
G: TNFRGraph,
|
|
31
|
+
node: NodeId,
|
|
32
|
+
t: float | int,
|
|
33
|
+
*,
|
|
34
|
+
strict: bool = ...,
|
|
35
|
+
log_level: int | None = ...,
|
|
36
|
+
) -> float: ...
|
tnfr/glyph_history.py
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
"""Utilities for tracking
|
|
1
|
+
"""Utilities for tracking structural operator emission history and related metrics.
|
|
2
|
+
|
|
3
|
+
This module tracks the history of glyphs (structural symbols like AL, EN, IL, etc.)
|
|
4
|
+
that are emitted when structural operators (Emission, Reception, Coherence, etc.)
|
|
5
|
+
are applied to nodes in the TNFR network.
|
|
6
|
+
"""
|
|
2
7
|
|
|
3
8
|
from __future__ import annotations
|
|
4
9
|
|
|
5
|
-
from
|
|
6
|
-
from collections import
|
|
10
|
+
from collections import Counter, deque
|
|
11
|
+
from collections.abc import Iterable, Mapping, MutableMapping
|
|
7
12
|
from itertools import islice
|
|
8
|
-
from
|
|
9
|
-
from functools import lru_cache
|
|
13
|
+
from typing import Any, cast
|
|
10
14
|
|
|
11
|
-
from .constants import get_param
|
|
12
|
-
from .
|
|
13
|
-
from .
|
|
15
|
+
from .constants import get_param, normalise_state_token
|
|
16
|
+
from .glyph_runtime import last_glyph
|
|
17
|
+
from .types import TNFRGraph
|
|
18
|
+
from .utils import ensure_collection, get_logger
|
|
14
19
|
|
|
15
20
|
logger = get_logger(__name__)
|
|
16
21
|
|
|
@@ -21,28 +26,27 @@ __all__ = (
|
|
|
21
26
|
"ensure_history",
|
|
22
27
|
"current_step_idx",
|
|
23
28
|
"append_metric",
|
|
24
|
-
"last_glyph",
|
|
25
29
|
"count_glyphs",
|
|
26
30
|
)
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _validate_window(window: int, *, positive: bool = False) -> int:
|
|
37
|
-
return _resolve_validate_window()(window, positive=positive)
|
|
32
|
+
_NU_F_HISTORY_KEYS = (
|
|
33
|
+
"nu_f_rate_hz_str",
|
|
34
|
+
"nu_f_rate_hz",
|
|
35
|
+
"nu_f_ci_lower_hz_str",
|
|
36
|
+
"nu_f_ci_upper_hz_str",
|
|
37
|
+
"nu_f_ci_lower_hz",
|
|
38
|
+
"nu_f_ci_upper_hz",
|
|
39
|
+
)
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def _ensure_history(
|
|
41
|
-
nd:
|
|
42
|
-
) -> tuple[int, deque | None]:
|
|
43
|
+
nd: MutableMapping[str, Any], window: int, *, create_zero: bool = False
|
|
44
|
+
) -> tuple[int, deque[str] | None]:
|
|
43
45
|
"""Validate ``window`` and ensure ``nd['glyph_history']`` deque."""
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
from tnfr.validation.window import validate_window
|
|
48
|
+
|
|
49
|
+
v_window = validate_window(window)
|
|
46
50
|
if v_window == 0 and not create_zero:
|
|
47
51
|
return v_window, None
|
|
48
52
|
hist = nd.setdefault("glyph_history", deque(maxlen=v_window))
|
|
@@ -54,16 +58,14 @@ def _ensure_history(
|
|
|
54
58
|
try:
|
|
55
59
|
items = ensure_collection(hist, max_materialize=None)
|
|
56
60
|
except TypeError:
|
|
57
|
-
logger.debug(
|
|
58
|
-
"Discarding non-iterable glyph history value %r", hist
|
|
59
|
-
)
|
|
61
|
+
logger.debug("Discarding non-iterable glyph history value %r", hist)
|
|
60
62
|
items = ()
|
|
61
|
-
hist = deque(items, maxlen=v_window)
|
|
63
|
+
hist = deque((str(item) for item in items), maxlen=v_window)
|
|
62
64
|
nd["glyph_history"] = hist
|
|
63
65
|
return v_window, hist
|
|
64
66
|
|
|
65
67
|
|
|
66
|
-
def push_glyph(nd:
|
|
68
|
+
def push_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> None:
|
|
67
69
|
"""Add ``glyph`` to node history with maximum size ``window``.
|
|
68
70
|
|
|
69
71
|
``window`` validation and deque creation are handled by
|
|
@@ -74,22 +76,47 @@ def push_glyph(nd: dict[str, Any], glyph: str, window: int) -> None:
|
|
|
74
76
|
hist.append(str(glyph))
|
|
75
77
|
|
|
76
78
|
|
|
77
|
-
def recent_glyph(nd:
|
|
79
|
+
def recent_glyph(nd: MutableMapping[str, Any], glyph: str, window: int) -> bool:
|
|
78
80
|
"""Return ``True`` if ``glyph`` appeared in last ``window`` emissions.
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
This is a **read-only** operation that checks the existing history without
|
|
83
|
+
modifying it. If ``window`` is zero, returns ``False``. Negative values
|
|
84
|
+
raise :class:`ValueError`.
|
|
85
|
+
|
|
86
|
+
Notes
|
|
87
|
+
-----
|
|
88
|
+
This function intentionally does NOT call ``_ensure_history`` to avoid
|
|
89
|
+
accidentally truncating the glyph_history deque when checking with a
|
|
90
|
+
smaller window than the deque's maxlen. This preserves the canonical
|
|
91
|
+
principle that reading history should not modify it.
|
|
92
|
+
|
|
93
|
+
Reuses ``validate_window`` and ``ensure_collection`` utilities.
|
|
83
94
|
"""
|
|
95
|
+
from tnfr.validation.window import validate_window
|
|
84
96
|
|
|
85
|
-
v_window
|
|
97
|
+
v_window = validate_window(window)
|
|
86
98
|
if v_window == 0:
|
|
87
99
|
return False
|
|
100
|
+
|
|
101
|
+
# Read existing history without modifying it
|
|
102
|
+
hist = nd.get("glyph_history")
|
|
103
|
+
if hist is None:
|
|
104
|
+
return False
|
|
105
|
+
|
|
88
106
|
gl = str(glyph)
|
|
89
|
-
|
|
107
|
+
|
|
108
|
+
# Use canonical ensure_collection to materialize history
|
|
109
|
+
try:
|
|
110
|
+
items = list(ensure_collection(hist, max_materialize=None))
|
|
111
|
+
except (TypeError, ValueError):
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
# Check only the last v_window items
|
|
115
|
+
recent_items = items[-v_window:] if len(items) > v_window else items
|
|
116
|
+
return gl in recent_items
|
|
90
117
|
|
|
91
118
|
|
|
92
|
-
class HistoryDict(dict):
|
|
119
|
+
class HistoryDict(dict[str, Any]):
|
|
93
120
|
"""Dict specialized for bounded history series and usage counts.
|
|
94
121
|
|
|
95
122
|
Usage counts are tracked explicitly via :meth:`get_increment`. Accessing
|
|
@@ -108,7 +135,7 @@ class HistoryDict(dict):
|
|
|
108
135
|
|
|
109
136
|
def __init__(
|
|
110
137
|
self,
|
|
111
|
-
data:
|
|
138
|
+
data: Mapping[str, Any] | None = None,
|
|
112
139
|
*,
|
|
113
140
|
maxlen: int = 0,
|
|
114
141
|
) -> None:
|
|
@@ -129,7 +156,7 @@ class HistoryDict(dict):
|
|
|
129
156
|
"""Increase usage count for ``key``."""
|
|
130
157
|
self._counts[key] += 1
|
|
131
158
|
|
|
132
|
-
def _to_deque(self, val: Any) -> deque:
|
|
159
|
+
def _to_deque(self, val: Any) -> deque[Any]:
|
|
133
160
|
"""Coerce ``val`` to a deque respecting ``self._maxlen``.
|
|
134
161
|
|
|
135
162
|
``Iterable`` inputs (excluding ``str`` and ``bytes``) are expanded into
|
|
@@ -155,26 +182,36 @@ class HistoryDict(dict):
|
|
|
155
182
|
return val
|
|
156
183
|
|
|
157
184
|
def get_increment(self, key: str, default: Any = None) -> Any:
|
|
185
|
+
"""Return value for ``key`` and increment its usage counter."""
|
|
186
|
+
|
|
158
187
|
insert = key not in self
|
|
159
188
|
val = self._resolve_value(key, default, insert=insert)
|
|
160
189
|
self._increment(key)
|
|
161
190
|
return val
|
|
162
191
|
|
|
163
|
-
def __getitem__(self, key): # type: ignore[override]
|
|
192
|
+
def __getitem__(self, key: str) -> Any: # type: ignore[override]
|
|
193
|
+
"""Return the tracked value for ``key`` ensuring deque normalisation."""
|
|
194
|
+
|
|
164
195
|
return self._resolve_value(key, None, insert=False)
|
|
165
196
|
|
|
166
|
-
def get(self, key, default=None): # type: ignore[override]
|
|
197
|
+
def get(self, key: str, default: Any | None = None) -> Any: # type: ignore[override]
|
|
198
|
+
"""Return ``key`` when present; otherwise fall back to ``default``."""
|
|
199
|
+
|
|
167
200
|
try:
|
|
168
201
|
return self._resolve_value(key, None, insert=False)
|
|
169
202
|
except KeyError:
|
|
170
203
|
return default
|
|
171
204
|
|
|
172
|
-
def __setitem__(self, key, value): # type: ignore[override]
|
|
205
|
+
def __setitem__(self, key: str, value: Any) -> None: # type: ignore[override]
|
|
206
|
+
"""Store ``value`` for ``key`` while initialising usage tracking."""
|
|
207
|
+
|
|
173
208
|
super().__setitem__(key, value)
|
|
174
209
|
if key not in self._counts:
|
|
175
210
|
self._counts[key] = 0
|
|
176
211
|
|
|
177
|
-
def setdefault(self, key, default=None): # type: ignore[override]
|
|
212
|
+
def setdefault(self, key: str, default: Any | None = None) -> Any: # type: ignore[override]
|
|
213
|
+
"""Return existing value for ``key`` or insert ``default`` when absent."""
|
|
214
|
+
|
|
178
215
|
insert = key not in self
|
|
179
216
|
val = self._resolve_value(key, default, insert=insert)
|
|
180
217
|
if insert:
|
|
@@ -191,6 +228,8 @@ class HistoryDict(dict):
|
|
|
191
228
|
raise KeyError("HistoryDict is empty; cannot pop least used")
|
|
192
229
|
|
|
193
230
|
def pop_least_used_batch(self, k: int) -> None:
|
|
231
|
+
"""Remove up to ``k`` least-used entries from the history."""
|
|
232
|
+
|
|
194
233
|
for _ in range(max(0, int(k))):
|
|
195
234
|
try:
|
|
196
235
|
self.pop_least_used()
|
|
@@ -198,7 +237,7 @@ class HistoryDict(dict):
|
|
|
198
237
|
break
|
|
199
238
|
|
|
200
239
|
|
|
201
|
-
def ensure_history(G) -> dict[str, Any]:
|
|
240
|
+
def ensure_history(G: TNFRGraph) -> HistoryDict | dict[str, Any]:
|
|
202
241
|
"""Ensure ``G.graph['history']`` exists and return it.
|
|
203
242
|
|
|
204
243
|
``HISTORY_MAXLEN`` must be non-negative; otherwise a
|
|
@@ -220,11 +259,10 @@ def ensure_history(G) -> dict[str, Any]:
|
|
|
220
259
|
replaced = True
|
|
221
260
|
if replaced:
|
|
222
261
|
G.graph.pop(sentinel_key, None)
|
|
262
|
+
if isinstance(hist, MutableMapping):
|
|
263
|
+
_normalise_state_streams(hist)
|
|
223
264
|
return hist
|
|
224
|
-
if (
|
|
225
|
-
not isinstance(hist, HistoryDict)
|
|
226
|
-
or hist._maxlen != maxlen
|
|
227
|
-
):
|
|
265
|
+
if not isinstance(hist, HistoryDict) or hist._maxlen != maxlen:
|
|
228
266
|
hist = HistoryDict(hist, maxlen=maxlen)
|
|
229
267
|
G.graph["history"] = hist
|
|
230
268
|
replaced = True
|
|
@@ -233,31 +271,44 @@ def ensure_history(G) -> dict[str, Any]:
|
|
|
233
271
|
hist.pop_least_used_batch(excess)
|
|
234
272
|
if replaced:
|
|
235
273
|
G.graph.pop(sentinel_key, None)
|
|
274
|
+
_normalise_state_streams(cast(MutableMapping[str, Any], hist))
|
|
236
275
|
return hist
|
|
237
276
|
|
|
238
277
|
|
|
239
|
-
def current_step_idx(G) -> int:
|
|
278
|
+
def current_step_idx(G: TNFRGraph | Mapping[str, Any]) -> int:
|
|
240
279
|
"""Return the current step index from ``G`` history."""
|
|
241
280
|
|
|
242
281
|
graph = getattr(G, "graph", G)
|
|
243
282
|
return len(graph.get("history", {}).get("C_steps", []))
|
|
244
283
|
|
|
245
|
-
|
|
246
284
|
|
|
247
|
-
def append_metric(hist:
|
|
285
|
+
def append_metric(hist: MutableMapping[str, list[Any]], key: str, value: Any) -> None:
|
|
248
286
|
"""Append ``value`` to ``hist[key]`` list, creating it if missing."""
|
|
249
|
-
|
|
250
|
-
|
|
287
|
+
if key == "phase_state" and isinstance(value, str):
|
|
288
|
+
value = normalise_state_token(value)
|
|
289
|
+
elif key == "nodal_diag" and isinstance(value, Mapping):
|
|
290
|
+
snapshot: dict[Any, Any] = {}
|
|
291
|
+
for node, payload in value.items():
|
|
292
|
+
if isinstance(payload, Mapping):
|
|
293
|
+
state_value = payload.get("state")
|
|
294
|
+
if isinstance(payload, MutableMapping):
|
|
295
|
+
updated = payload
|
|
296
|
+
else:
|
|
297
|
+
updated = dict(payload)
|
|
298
|
+
if isinstance(state_value, str):
|
|
299
|
+
updated["state"] = normalise_state_token(state_value)
|
|
300
|
+
snapshot[node] = updated
|
|
301
|
+
else:
|
|
302
|
+
snapshot[node] = payload
|
|
303
|
+
hist.setdefault(key, []).append(snapshot)
|
|
304
|
+
return
|
|
251
305
|
|
|
252
|
-
|
|
253
|
-
"""Return the most recent glyph for node or ``None``."""
|
|
254
|
-
hist = nd.get("glyph_history")
|
|
255
|
-
return hist[-1] if hist else None
|
|
306
|
+
hist.setdefault(key, []).append(value)
|
|
256
307
|
|
|
257
308
|
|
|
258
309
|
def count_glyphs(
|
|
259
|
-
G, window: int | None = None, *, last_only: bool = False
|
|
260
|
-
) -> Counter:
|
|
310
|
+
G: TNFRGraph, window: int | None = None, *, last_only: bool = False
|
|
311
|
+
) -> Counter[str]:
|
|
261
312
|
"""Count recent glyphs in the network.
|
|
262
313
|
|
|
263
314
|
If ``window`` is ``None``, the full history for each node is used. A
|
|
@@ -266,7 +317,9 @@ def count_glyphs(
|
|
|
266
317
|
"""
|
|
267
318
|
|
|
268
319
|
if window is not None:
|
|
269
|
-
window
|
|
320
|
+
from tnfr.validation.window import validate_window
|
|
321
|
+
|
|
322
|
+
window = validate_window(window)
|
|
270
323
|
if window == 0:
|
|
271
324
|
return Counter()
|
|
272
325
|
|
|
@@ -288,3 +341,37 @@ def count_glyphs(
|
|
|
288
341
|
counts.update(seq)
|
|
289
342
|
|
|
290
343
|
return counts
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _normalise_state_streams(hist: MutableMapping[str, Any]) -> None:
|
|
347
|
+
"""Normalise legacy state tokens stored in telemetry history."""
|
|
348
|
+
|
|
349
|
+
phase_state = hist.get("phase_state")
|
|
350
|
+
if isinstance(phase_state, deque):
|
|
351
|
+
canonical = [normalise_state_token(str(item)) for item in phase_state]
|
|
352
|
+
if canonical != list(phase_state):
|
|
353
|
+
phase_state.clear()
|
|
354
|
+
phase_state.extend(canonical)
|
|
355
|
+
elif isinstance(phase_state, list):
|
|
356
|
+
canonical = [normalise_state_token(str(item)) for item in phase_state]
|
|
357
|
+
if canonical != phase_state:
|
|
358
|
+
hist["phase_state"] = canonical
|
|
359
|
+
|
|
360
|
+
diag_history = hist.get("nodal_diag")
|
|
361
|
+
if isinstance(diag_history, list):
|
|
362
|
+
for snapshot in diag_history:
|
|
363
|
+
if not isinstance(snapshot, Mapping):
|
|
364
|
+
continue
|
|
365
|
+
for node, payload in snapshot.items():
|
|
366
|
+
if not isinstance(payload, Mapping):
|
|
367
|
+
continue
|
|
368
|
+
state_value = payload.get("state")
|
|
369
|
+
if not isinstance(state_value, str):
|
|
370
|
+
continue
|
|
371
|
+
canonical = normalise_state_token(state_value)
|
|
372
|
+
if canonical == state_value:
|
|
373
|
+
continue
|
|
374
|
+
if isinstance(payload, MutableMapping):
|
|
375
|
+
payload["state"] = canonical
|
|
376
|
+
else:
|
|
377
|
+
snapshot[node] = {**payload, "state": canonical}
|