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/metrics/trig.py
CHANGED
|
@@ -7,18 +7,23 @@ Caching of cosine/sine values lives in :mod:`tnfr.metrics.trig_cache`.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import math
|
|
10
|
-
from collections.abc import Iterable, Sequence
|
|
10
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
11
11
|
from itertools import tee
|
|
12
|
-
from typing import Any
|
|
12
|
+
from typing import TYPE_CHECKING, Any, cast, overload
|
|
13
13
|
|
|
14
|
-
from ..
|
|
15
|
-
from ..
|
|
14
|
+
from ..utils import kahan_sum_nd
|
|
15
|
+
from ..types import NodeId, Phase, TNFRGraph
|
|
16
|
+
from ..utils import cached_import, get_numpy
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
19
|
+
from ..node import NodeProtocol
|
|
16
20
|
|
|
17
21
|
__all__ = (
|
|
18
22
|
"accumulate_cos_sin",
|
|
19
23
|
"_phase_mean_from_iter",
|
|
20
24
|
"_neighbor_phase_mean_core",
|
|
21
25
|
"_neighbor_phase_mean_generic",
|
|
26
|
+
"neighbor_phase_mean_bulk",
|
|
22
27
|
"neighbor_phase_mean_list",
|
|
23
28
|
"neighbor_phase_mean",
|
|
24
29
|
)
|
|
@@ -37,7 +42,7 @@ def accumulate_cos_sin(
|
|
|
37
42
|
|
|
38
43
|
processed = False
|
|
39
44
|
|
|
40
|
-
def iter_real_pairs():
|
|
45
|
+
def iter_real_pairs() -> Iterator[tuple[float, float]]:
|
|
41
46
|
nonlocal processed
|
|
42
47
|
for cs in it:
|
|
43
48
|
if cs is None:
|
|
@@ -82,12 +87,12 @@ def _neighbor_phase_mean_core(
|
|
|
82
87
|
neigh: Sequence[Any],
|
|
83
88
|
cos_map: dict[Any, float],
|
|
84
89
|
sin_map: dict[Any, float],
|
|
85
|
-
np,
|
|
90
|
+
np: Any | None,
|
|
86
91
|
fallback: float,
|
|
87
92
|
) -> float:
|
|
88
93
|
"""Return circular mean of neighbour phases given trig mappings."""
|
|
89
94
|
|
|
90
|
-
def _iter_pairs():
|
|
95
|
+
def _iter_pairs() -> Iterator[tuple[float, float]]:
|
|
91
96
|
for v in neigh:
|
|
92
97
|
c = cos_map.get(v)
|
|
93
98
|
s = sin_map.get(v)
|
|
@@ -113,13 +118,13 @@ def _neighbor_phase_mean_core(
|
|
|
113
118
|
|
|
114
119
|
|
|
115
120
|
def _neighbor_phase_mean_generic(
|
|
116
|
-
obj,
|
|
121
|
+
obj: "NodeProtocol" | Sequence[Any],
|
|
117
122
|
cos_map: dict[Any, float] | None = None,
|
|
118
123
|
sin_map: dict[Any, float] | None = None,
|
|
119
|
-
np=None,
|
|
124
|
+
np: Any | None = None,
|
|
120
125
|
fallback: float = 0.0,
|
|
121
126
|
) -> float:
|
|
122
|
-
"""
|
|
127
|
+
"""Compute the neighbour phase mean via :func:`_neighbor_phase_mean_core`.
|
|
123
128
|
|
|
124
129
|
``obj`` may be either a node bound to a graph or a sequence of neighbours.
|
|
125
130
|
When ``cos_map`` and ``sin_map`` are ``None`` the function assumes ``obj`` is
|
|
@@ -132,11 +137,9 @@ def _neighbor_phase_mean_generic(
|
|
|
132
137
|
np = get_numpy()
|
|
133
138
|
|
|
134
139
|
if cos_map is None or sin_map is None:
|
|
135
|
-
node = obj
|
|
140
|
+
node = cast("NodeProtocol", obj)
|
|
136
141
|
if getattr(node, "G", None) is None:
|
|
137
|
-
raise TypeError(
|
|
138
|
-
"neighbor_phase_mean requires nodes bound to a graph"
|
|
139
|
-
)
|
|
142
|
+
raise TypeError("neighbor_phase_mean requires nodes bound to a graph")
|
|
140
143
|
from .trig_cache import get_trig_cache
|
|
141
144
|
|
|
142
145
|
trig = get_trig_cache(node.G)
|
|
@@ -145,7 +148,7 @@ def _neighbor_phase_mean_generic(
|
|
|
145
148
|
sin_map = trig.sin
|
|
146
149
|
neigh = node.G[node.n]
|
|
147
150
|
else:
|
|
148
|
-
neigh = obj
|
|
151
|
+
neigh = cast(Sequence[Any], obj)
|
|
149
152
|
|
|
150
153
|
return _neighbor_phase_mean_core(neigh, cos_map, sin_map, np, fallback)
|
|
151
154
|
|
|
@@ -154,7 +157,7 @@ def neighbor_phase_mean_list(
|
|
|
154
157
|
neigh: Sequence[Any],
|
|
155
158
|
cos_th: dict[Any, float],
|
|
156
159
|
sin_th: dict[Any, float],
|
|
157
|
-
np=None,
|
|
160
|
+
np: Any | None = None,
|
|
158
161
|
fallback: float = 0.0,
|
|
159
162
|
) -> float:
|
|
160
163
|
"""Return circular mean of neighbour phases from cosine/sine mappings.
|
|
@@ -168,14 +171,203 @@ def neighbor_phase_mean_list(
|
|
|
168
171
|
)
|
|
169
172
|
|
|
170
173
|
|
|
171
|
-
def
|
|
172
|
-
|
|
174
|
+
def neighbor_phase_mean_bulk(
|
|
175
|
+
edge_src: Any,
|
|
176
|
+
edge_dst: Any,
|
|
177
|
+
*,
|
|
178
|
+
cos_values: Any,
|
|
179
|
+
sin_values: Any,
|
|
180
|
+
theta_values: Any,
|
|
181
|
+
node_count: int,
|
|
182
|
+
np: Any,
|
|
183
|
+
neighbor_cos_sum: Any | None = None,
|
|
184
|
+
neighbor_sin_sum: Any | None = None,
|
|
185
|
+
neighbor_counts: Any | None = None,
|
|
186
|
+
mean_cos: Any | None = None,
|
|
187
|
+
mean_sin: Any | None = None,
|
|
188
|
+
) -> tuple[Any, Any]:
|
|
189
|
+
"""Vectorised neighbour phase means for all nodes in a graph.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
edge_src, edge_dst:
|
|
194
|
+
Arrays describing the source (neighbour) and destination (node) indices
|
|
195
|
+
for each edge contribution. They must have matching shapes.
|
|
196
|
+
cos_values, sin_values:
|
|
197
|
+
Arrays containing the cosine and sine values of each node's phase. The
|
|
198
|
+
arrays must be indexed using the same positional indices referenced by
|
|
199
|
+
``edge_src``.
|
|
200
|
+
theta_values:
|
|
201
|
+
Array with the baseline phase for each node. Positions that do not have
|
|
202
|
+
neighbours reuse this baseline as their mean phase.
|
|
203
|
+
node_count:
|
|
204
|
+
Total number of nodes represented in ``theta_values``.
|
|
205
|
+
np:
|
|
206
|
+
Numpy module used to materialise the vectorised operations.
|
|
207
|
+
|
|
208
|
+
Optional buffers
|
|
209
|
+
-----------------
|
|
210
|
+
neighbor_cos_sum, neighbor_sin_sum, neighbor_counts, mean_cos, mean_sin:
|
|
211
|
+
Preallocated arrays sized ``node_count`` reused to accumulate the
|
|
212
|
+
neighbour cosine/sine sums, neighbour sample counts, and the averaged
|
|
213
|
+
cosine/sine vectors. When omitted, the helper materialises fresh
|
|
214
|
+
buffers that match the previous semantics.
|
|
215
|
+
|
|
216
|
+
Returns
|
|
217
|
+
-------
|
|
218
|
+
tuple[Any, Any]
|
|
219
|
+
Tuple ``(mean_theta, has_neighbors)`` where ``mean_theta`` contains the
|
|
220
|
+
circular mean of neighbour phases for every node and ``has_neighbors``
|
|
221
|
+
is a boolean mask identifying which nodes contributed at least one
|
|
222
|
+
neighbour sample.
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
if node_count <= 0:
|
|
226
|
+
empty_mean = np.zeros(0, dtype=float)
|
|
227
|
+
return empty_mean, empty_mean.astype(bool)
|
|
228
|
+
|
|
229
|
+
edge_src_arr = np.asarray(edge_src, dtype=np.intp)
|
|
230
|
+
edge_dst_arr = np.asarray(edge_dst, dtype=np.intp)
|
|
231
|
+
|
|
232
|
+
if edge_src_arr.shape != edge_dst_arr.shape:
|
|
233
|
+
raise ValueError("edge_src and edge_dst must share the same shape")
|
|
234
|
+
|
|
235
|
+
theta_arr = np.asarray(theta_values, dtype=float)
|
|
236
|
+
if theta_arr.ndim != 1 or theta_arr.size != node_count:
|
|
237
|
+
raise ValueError("theta_values must be a 1-D array matching node_count")
|
|
173
238
|
|
|
174
|
-
|
|
239
|
+
cos_arr = np.asarray(cos_values, dtype=float)
|
|
240
|
+
sin_arr = np.asarray(sin_values, dtype=float)
|
|
241
|
+
if cos_arr.ndim != 1 or cos_arr.size != node_count:
|
|
242
|
+
raise ValueError("cos_values must be a 1-D array matching node_count")
|
|
243
|
+
if sin_arr.ndim != 1 or sin_arr.size != node_count:
|
|
244
|
+
raise ValueError("sin_values must be a 1-D array matching node_count")
|
|
245
|
+
|
|
246
|
+
edge_count = edge_dst_arr.size
|
|
247
|
+
|
|
248
|
+
def _coerce_buffer(buffer: Any | None, *, name: str) -> tuple[Any, bool]:
|
|
249
|
+
if buffer is None:
|
|
250
|
+
return None, False
|
|
251
|
+
arr = np.array(buffer, dtype=float, copy=False)
|
|
252
|
+
if arr.ndim != 1 or arr.size != node_count:
|
|
253
|
+
raise ValueError(f"{name} must be a 1-D array sized node_count")
|
|
254
|
+
arr.fill(0.0)
|
|
255
|
+
return arr, True
|
|
256
|
+
|
|
257
|
+
neighbor_cos_sum, has_cos_buffer = _coerce_buffer(
|
|
258
|
+
neighbor_cos_sum, name="neighbor_cos_sum"
|
|
259
|
+
)
|
|
260
|
+
neighbor_sin_sum, has_sin_buffer = _coerce_buffer(
|
|
261
|
+
neighbor_sin_sum, name="neighbor_sin_sum"
|
|
262
|
+
)
|
|
263
|
+
neighbor_counts, has_count_buffer = _coerce_buffer(
|
|
264
|
+
neighbor_counts, name="neighbor_counts"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if edge_count:
|
|
268
|
+
cos_bincount = np.bincount(
|
|
269
|
+
edge_dst_arr,
|
|
270
|
+
weights=cos_arr[edge_src_arr],
|
|
271
|
+
minlength=node_count,
|
|
272
|
+
)
|
|
273
|
+
sin_bincount = np.bincount(
|
|
274
|
+
edge_dst_arr,
|
|
275
|
+
weights=sin_arr[edge_src_arr],
|
|
276
|
+
minlength=node_count,
|
|
277
|
+
)
|
|
278
|
+
count_bincount = np.bincount(
|
|
279
|
+
edge_dst_arr,
|
|
280
|
+
minlength=node_count,
|
|
281
|
+
).astype(float, copy=False)
|
|
282
|
+
|
|
283
|
+
if not has_cos_buffer:
|
|
284
|
+
neighbor_cos_sum = cos_bincount
|
|
285
|
+
else:
|
|
286
|
+
np.copyto(neighbor_cos_sum, cos_bincount)
|
|
287
|
+
|
|
288
|
+
if not has_sin_buffer:
|
|
289
|
+
neighbor_sin_sum = sin_bincount
|
|
290
|
+
else:
|
|
291
|
+
np.copyto(neighbor_sin_sum, sin_bincount)
|
|
292
|
+
|
|
293
|
+
if not has_count_buffer:
|
|
294
|
+
neighbor_counts = count_bincount
|
|
295
|
+
else:
|
|
296
|
+
np.copyto(neighbor_counts, count_bincount)
|
|
297
|
+
else:
|
|
298
|
+
if neighbor_cos_sum is None:
|
|
299
|
+
neighbor_cos_sum = np.zeros(node_count, dtype=float)
|
|
300
|
+
if neighbor_sin_sum is None:
|
|
301
|
+
neighbor_sin_sum = np.zeros(node_count, dtype=float)
|
|
302
|
+
if neighbor_counts is None:
|
|
303
|
+
neighbor_counts = np.zeros(node_count, dtype=float)
|
|
304
|
+
|
|
305
|
+
has_neighbors = neighbor_counts > 0.0
|
|
306
|
+
|
|
307
|
+
mean_cos, _ = _coerce_buffer(mean_cos, name="mean_cos")
|
|
308
|
+
mean_sin, _ = _coerce_buffer(mean_sin, name="mean_sin")
|
|
309
|
+
|
|
310
|
+
if mean_cos is None:
|
|
311
|
+
mean_cos = np.zeros(node_count, dtype=float)
|
|
312
|
+
if mean_sin is None:
|
|
313
|
+
mean_sin = np.zeros(node_count, dtype=float)
|
|
314
|
+
|
|
315
|
+
if edge_count:
|
|
316
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
317
|
+
np.divide(
|
|
318
|
+
neighbor_cos_sum,
|
|
319
|
+
neighbor_counts,
|
|
320
|
+
out=mean_cos,
|
|
321
|
+
where=has_neighbors,
|
|
322
|
+
)
|
|
323
|
+
np.divide(
|
|
324
|
+
neighbor_sin_sum,
|
|
325
|
+
neighbor_counts,
|
|
326
|
+
out=mean_sin,
|
|
327
|
+
where=has_neighbors,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
mean_theta = np.where(has_neighbors, np.arctan2(mean_sin, mean_cos), theta_arr)
|
|
331
|
+
return mean_theta, has_neighbors
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@overload
|
|
335
|
+
def neighbor_phase_mean(obj: "NodeProtocol", n: None = ...) -> Phase: ...
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@overload
|
|
339
|
+
def neighbor_phase_mean(obj: TNFRGraph, n: NodeId) -> Phase: ...
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def neighbor_phase_mean(
|
|
343
|
+
obj: "NodeProtocol" | TNFRGraph, n: NodeId | None = None
|
|
344
|
+
) -> Phase:
|
|
345
|
+
"""Circular mean of neighbour phases for ``obj``.
|
|
346
|
+
|
|
347
|
+
Parameters
|
|
348
|
+
----------
|
|
349
|
+
obj:
|
|
350
|
+
Either a :class:`~tnfr.node.NodeProtocol` instance bound to a graph or a
|
|
351
|
+
:class:`~tnfr.types.TNFRGraph` from which the node ``n`` will be wrapped.
|
|
352
|
+
n:
|
|
353
|
+
Optional node identifier. Required when ``obj`` is a graph. Providing a
|
|
354
|
+
node identifier for a node object raises :class:`TypeError`.
|
|
175
355
|
"""
|
|
176
356
|
|
|
177
|
-
|
|
178
|
-
if
|
|
179
|
-
raise ImportError("
|
|
180
|
-
|
|
357
|
+
NodeNX = cached_import("tnfr.node", "NodeNX")
|
|
358
|
+
if NodeNX is None:
|
|
359
|
+
raise ImportError("NodeNX is unavailable")
|
|
360
|
+
if n is None:
|
|
361
|
+
if hasattr(obj, "nodes"):
|
|
362
|
+
raise TypeError(
|
|
363
|
+
"neighbor_phase_mean requires a node identifier when passing a graph"
|
|
364
|
+
)
|
|
365
|
+
node = obj
|
|
366
|
+
else:
|
|
367
|
+
if hasattr(obj, "nodes"):
|
|
368
|
+
node = NodeNX(obj, n)
|
|
369
|
+
else:
|
|
370
|
+
raise TypeError(
|
|
371
|
+
"neighbor_phase_mean received a node and an explicit identifier"
|
|
372
|
+
)
|
|
181
373
|
return _neighbor_phase_mean_generic(node)
|
tnfr/metrics/trig.pyi
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
__all__: Any
|
|
4
|
+
|
|
5
|
+
def __getattr__(name: str) -> Any: ...
|
|
6
|
+
|
|
7
|
+
_neighbor_phase_mean_core: Any
|
|
8
|
+
_neighbor_phase_mean_generic: Any
|
|
9
|
+
_phase_mean_from_iter: Any
|
|
10
|
+
accumulate_cos_sin: Any
|
|
11
|
+
neighbor_phase_mean: Any
|
|
12
|
+
neighbor_phase_mean_bulk: Any
|
|
13
|
+
neighbor_phase_mean_list: Any
|
tnfr/metrics/trig_cache.py
CHANGED
|
@@ -6,17 +6,15 @@ focused on pure mathematical utilities (phase means, compensated sums, etc.).
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
from ..compat.dataclass import dataclass
|
|
10
|
+
import hashlib
|
|
9
11
|
import math
|
|
10
|
-
|
|
12
|
+
import struct
|
|
11
13
|
from typing import Any, Iterable, Mapping
|
|
12
14
|
|
|
13
|
-
from ..alias import
|
|
14
|
-
from ..
|
|
15
|
-
from ..
|
|
16
|
-
from ..import_utils import get_numpy
|
|
17
|
-
from ..types import GraphLike
|
|
18
|
-
|
|
19
|
-
ALIAS_THETA = get_aliases("THETA")
|
|
15
|
+
from ..alias import get_theta_attr
|
|
16
|
+
from ..types import GraphLike, NodeAttrMap
|
|
17
|
+
from ..utils import edge_version_cache, get_numpy
|
|
20
18
|
|
|
21
19
|
__all__ = ("TrigCache", "compute_theta_trig", "get_trig_cache", "_compute_trig_python")
|
|
22
20
|
|
|
@@ -28,37 +26,71 @@ class TrigCache:
|
|
|
28
26
|
cos: dict[Any, float]
|
|
29
27
|
sin: dict[Any, float]
|
|
30
28
|
theta: dict[Any, float]
|
|
29
|
+
theta_checksums: dict[Any, bytes]
|
|
30
|
+
order: tuple[Any, ...]
|
|
31
|
+
cos_values: Any
|
|
32
|
+
sin_values: Any
|
|
33
|
+
theta_values: Any
|
|
34
|
+
index: dict[Any, int]
|
|
35
|
+
edge_src: Any | None = None
|
|
36
|
+
edge_dst: Any | None = None
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
def _iter_theta_pairs(
|
|
34
|
-
nodes: Iterable[tuple[Any,
|
|
40
|
+
nodes: Iterable[tuple[Any, NodeAttrMap | float]],
|
|
35
41
|
) -> Iterable[tuple[Any, float]]:
|
|
36
42
|
"""Yield ``(node, θ)`` pairs from ``nodes``."""
|
|
37
43
|
|
|
38
44
|
for n, data in nodes:
|
|
39
45
|
if isinstance(data, Mapping):
|
|
40
|
-
yield n,
|
|
46
|
+
yield n, get_theta_attr(data, 0.0) or 0.0
|
|
41
47
|
else:
|
|
42
48
|
yield n, float(data)
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
def _compute_trig_python(
|
|
46
|
-
nodes: Iterable[tuple[Any,
|
|
52
|
+
nodes: Iterable[tuple[Any, NodeAttrMap | float]],
|
|
47
53
|
) -> TrigCache:
|
|
48
54
|
"""Compute trigonometric mappings using pure Python."""
|
|
49
55
|
|
|
56
|
+
pairs = list(_iter_theta_pairs(nodes))
|
|
57
|
+
|
|
50
58
|
cos_th: dict[Any, float] = {}
|
|
51
59
|
sin_th: dict[Any, float] = {}
|
|
52
60
|
thetas: dict[Any, float] = {}
|
|
53
|
-
|
|
61
|
+
theta_checksums: dict[Any, bytes] = {}
|
|
62
|
+
order_list: list[Any] = []
|
|
63
|
+
|
|
64
|
+
for n, th in pairs:
|
|
65
|
+
order_list.append(n)
|
|
54
66
|
thetas[n] = th
|
|
55
67
|
cos_th[n] = math.cos(th)
|
|
56
68
|
sin_th[n] = math.sin(th)
|
|
57
|
-
|
|
69
|
+
theta_checksums[n] = _theta_checksum(th)
|
|
70
|
+
|
|
71
|
+
order = tuple(order_list)
|
|
72
|
+
cos_values = tuple(cos_th[n] for n in order)
|
|
73
|
+
sin_values = tuple(sin_th[n] for n in order)
|
|
74
|
+
theta_values = tuple(thetas[n] for n in order)
|
|
75
|
+
index = {n: i for i, n in enumerate(order)}
|
|
76
|
+
|
|
77
|
+
return TrigCache(
|
|
78
|
+
cos=cos_th,
|
|
79
|
+
sin=sin_th,
|
|
80
|
+
theta=thetas,
|
|
81
|
+
theta_checksums=theta_checksums,
|
|
82
|
+
order=order,
|
|
83
|
+
cos_values=cos_values,
|
|
84
|
+
sin_values=sin_values,
|
|
85
|
+
theta_values=theta_values,
|
|
86
|
+
index=index,
|
|
87
|
+
edge_src=None,
|
|
88
|
+
edge_dst=None,
|
|
89
|
+
)
|
|
58
90
|
|
|
59
91
|
|
|
60
92
|
def compute_theta_trig(
|
|
61
|
-
nodes: Iterable[tuple[Any,
|
|
93
|
+
nodes: Iterable[tuple[Any, NodeAttrMap | float]],
|
|
62
94
|
np: Any | None = None,
|
|
63
95
|
) -> TrigCache:
|
|
64
96
|
"""Return trigonometric mappings of ``θ`` per node."""
|
|
@@ -70,9 +102,22 @@ def compute_theta_trig(
|
|
|
70
102
|
|
|
71
103
|
pairs = list(_iter_theta_pairs(nodes))
|
|
72
104
|
if not pairs:
|
|
73
|
-
return TrigCache(
|
|
105
|
+
return TrigCache(
|
|
106
|
+
cos={},
|
|
107
|
+
sin={},
|
|
108
|
+
theta={},
|
|
109
|
+
theta_checksums={},
|
|
110
|
+
order=(),
|
|
111
|
+
cos_values=(),
|
|
112
|
+
sin_values=(),
|
|
113
|
+
theta_values=(),
|
|
114
|
+
index={},
|
|
115
|
+
edge_src=None,
|
|
116
|
+
edge_dst=None,
|
|
117
|
+
)
|
|
74
118
|
|
|
75
119
|
node_list, theta_vals = zip(*pairs)
|
|
120
|
+
node_list = tuple(node_list)
|
|
76
121
|
theta_arr = np.fromiter(theta_vals, dtype=float)
|
|
77
122
|
cos_arr = np.cos(theta_arr)
|
|
78
123
|
sin_arr = np.sin(theta_arr)
|
|
@@ -80,7 +125,21 @@ def compute_theta_trig(
|
|
|
80
125
|
cos_th = dict(zip(node_list, map(float, cos_arr)))
|
|
81
126
|
sin_th = dict(zip(node_list, map(float, sin_arr)))
|
|
82
127
|
thetas = dict(zip(node_list, map(float, theta_arr)))
|
|
83
|
-
|
|
128
|
+
theta_checksums = {node: _theta_checksum(float(theta)) for node, theta in pairs}
|
|
129
|
+
index = {n: i for i, n in enumerate(node_list)}
|
|
130
|
+
return TrigCache(
|
|
131
|
+
cos=cos_th,
|
|
132
|
+
sin=sin_th,
|
|
133
|
+
theta=thetas,
|
|
134
|
+
theta_checksums=theta_checksums,
|
|
135
|
+
order=node_list,
|
|
136
|
+
cos_values=cos_arr,
|
|
137
|
+
sin_values=sin_arr,
|
|
138
|
+
theta_values=theta_arr,
|
|
139
|
+
index=index,
|
|
140
|
+
edge_src=None,
|
|
141
|
+
edge_dst=None,
|
|
142
|
+
)
|
|
84
143
|
|
|
85
144
|
|
|
86
145
|
def _build_trig_cache(G: GraphLike, np: Any | None = None) -> TrigCache:
|
|
@@ -95,15 +154,80 @@ def get_trig_cache(
|
|
|
95
154
|
np: Any | None = None,
|
|
96
155
|
cache_size: int | None = 128,
|
|
97
156
|
) -> TrigCache:
|
|
98
|
-
"""Return cached cosines and sines of ``θ`` per node.
|
|
157
|
+
"""Return cached cosines and sines of ``θ`` per node.
|
|
158
|
+
|
|
159
|
+
This function maintains a cache of trigonometric values to avoid repeated
|
|
160
|
+
cos(θ) and sin(θ) computations across Si, coherence, and ΔNFR calculations.
|
|
161
|
+
The cache uses version-based invalidation triggered by theta attribute changes.
|
|
162
|
+
|
|
163
|
+
Cache Strategy
|
|
164
|
+
--------------
|
|
165
|
+
- **Key**: ``("_trig", version)`` where version increments on theta changes
|
|
166
|
+
- **Invalidation**: Checksum-based detection of theta attribute updates
|
|
167
|
+
- **Capacity**: Controlled by ``cache_size`` parameter (default: 128)
|
|
168
|
+
- **Scope**: Graph-wide, shared across all metrics computations
|
|
169
|
+
|
|
170
|
+
The cache maintains both dict (for sparse access) and array (for vectorized
|
|
171
|
+
operations) representations of the trigonometric values.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
G : GraphLike
|
|
176
|
+
Graph whose node theta attributes are cached.
|
|
177
|
+
np : Any or None, optional
|
|
178
|
+
NumPy module for array-based storage. Falls back to dict if None.
|
|
179
|
+
cache_size : int or None, optional
|
|
180
|
+
Maximum cache entries. Default: 128. None for unlimited.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
TrigCache
|
|
185
|
+
Container with cos/sin mappings and optional array representations.
|
|
186
|
+
See TrigCache dataclass for field documentation.
|
|
187
|
+
"""
|
|
99
188
|
|
|
100
189
|
if np is None:
|
|
101
190
|
np = get_numpy()
|
|
102
|
-
|
|
191
|
+
graph = G.graph
|
|
192
|
+
version = graph.setdefault("_trig_version", 0)
|
|
103
193
|
key = ("_trig", version)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
194
|
+
|
|
195
|
+
def builder() -> TrigCache:
|
|
196
|
+
return _build_trig_cache(G, np=np)
|
|
197
|
+
|
|
198
|
+
trig = edge_version_cache(G, key, builder, max_entries=cache_size)
|
|
199
|
+
current_checksums = _graph_theta_checksums(G)
|
|
200
|
+
trig_checksums = getattr(trig, "theta_checksums", None)
|
|
201
|
+
if trig_checksums is None:
|
|
202
|
+
trig_checksums = {}
|
|
203
|
+
|
|
204
|
+
# Checksum-based invalidation: detect theta attribute changes
|
|
205
|
+
if trig_checksums != current_checksums:
|
|
206
|
+
version = version + 1
|
|
207
|
+
graph["_trig_version"] = version
|
|
208
|
+
key = ("_trig", version)
|
|
209
|
+
trig = edge_version_cache(G, key, builder, max_entries=cache_size)
|
|
210
|
+
trig_checksums = getattr(trig, "theta_checksums", None)
|
|
211
|
+
if trig_checksums is None:
|
|
212
|
+
trig_checksums = {}
|
|
213
|
+
if trig_checksums != current_checksums:
|
|
214
|
+
current_checksums = _graph_theta_checksums(G)
|
|
215
|
+
if trig_checksums != current_checksums:
|
|
216
|
+
return trig
|
|
217
|
+
return trig
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _theta_checksum(theta: float) -> bytes:
|
|
221
|
+
"""Return a deterministic checksum for ``theta``."""
|
|
222
|
+
|
|
223
|
+
packed = struct.pack("!d", float(theta))
|
|
224
|
+
return hashlib.blake2b(packed, digest_size=8).digest()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _graph_theta_checksums(G: GraphLike) -> dict[Any, bytes]:
|
|
228
|
+
"""Return checksum snapshot of the graph's current ``θ`` values."""
|
|
229
|
+
|
|
230
|
+
checksums: dict[Any, bytes] = {}
|
|
231
|
+
for node, theta in _iter_theta_pairs(G.nodes(data=True)):
|
|
232
|
+
checksums[node] = _theta_checksum(theta)
|
|
233
|
+
return checksums
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Multi-scale hierarchical TNFR network support.
|
|
2
|
+
|
|
3
|
+
This module implements operational fractality (§3.7) by enabling TNFR networks
|
|
4
|
+
to operate recursively at multiple scales simultaneously, preserving structural
|
|
5
|
+
coherence across scale transitions.
|
|
6
|
+
|
|
7
|
+
Canonical Invariants Preserved
|
|
8
|
+
------------------------------
|
|
9
|
+
1. EPI operational fractality - nested EPIs maintain functional identity
|
|
10
|
+
2. Cross-scale ΔNFR - coherence propagates between scales
|
|
11
|
+
3. Phase synchrony - maintained within and across scales
|
|
12
|
+
4. Deterministic evolution - reproducible multi-scale dynamics
|
|
13
|
+
|
|
14
|
+
Examples
|
|
15
|
+
--------
|
|
16
|
+
Create a hierarchical network spanning quantum to organism scales:
|
|
17
|
+
|
|
18
|
+
>>> from tnfr.multiscale import HierarchicalTNFRNetwork, ScaleDefinition
|
|
19
|
+
>>> scales = [
|
|
20
|
+
... ScaleDefinition("quantum", node_count=1000, coupling_strength=0.8),
|
|
21
|
+
... ScaleDefinition("molecular", node_count=500, coupling_strength=0.6),
|
|
22
|
+
... ScaleDefinition("cellular", node_count=100, coupling_strength=0.4),
|
|
23
|
+
... ]
|
|
24
|
+
>>> network = HierarchicalTNFRNetwork(scales)
|
|
25
|
+
>>> # Network supports simultaneous evolution at all scales
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from .hierarchical import HierarchicalTNFRNetwork, ScaleDefinition
|
|
31
|
+
|
|
32
|
+
__all__ = ["HierarchicalTNFRNetwork", "ScaleDefinition"]
|