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
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
"""Hierarchical multi-scale TNFR network implementation.
|
|
2
|
+
|
|
3
|
+
Implements operational fractality by managing TNFR networks at multiple scales
|
|
4
|
+
with cross-scale coupling, preserving canonical TNFR invariants.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
12
|
+
|
|
13
|
+
import networkx as nx
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from ..types import DeltaNFR, NodeId, TNFRGraph
|
|
17
|
+
from ..structural import create_nfr
|
|
18
|
+
from ..dynamics import set_delta_nfr_hook, dnfr_epi_vf_mixed
|
|
19
|
+
from ..utils import get_logger
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class ScaleDefinition:
|
|
26
|
+
"""Definition of a single scale in a hierarchical TNFR network.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
name : str
|
|
31
|
+
Identifier for this scale (e.g., "quantum", "molecular", "cellular")
|
|
32
|
+
node_count : int
|
|
33
|
+
Number of nodes at this scale
|
|
34
|
+
coupling_strength : float
|
|
35
|
+
Base coupling strength for nodes within this scale (0.0 to 1.0)
|
|
36
|
+
edge_probability : float, optional
|
|
37
|
+
Probability of edge creation in Erdős-Rényi graph generation
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
name: str
|
|
41
|
+
node_count: int
|
|
42
|
+
coupling_strength: float
|
|
43
|
+
edge_probability: float = 0.1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class EvolutionResult:
|
|
48
|
+
"""Results from multi-scale evolution.
|
|
49
|
+
|
|
50
|
+
Attributes
|
|
51
|
+
----------
|
|
52
|
+
scale_results : Dict[str, Any]
|
|
53
|
+
Results indexed by scale name
|
|
54
|
+
total_coherence : float
|
|
55
|
+
Aggregated coherence across all scales
|
|
56
|
+
cross_scale_coupling : float
|
|
57
|
+
Measure of cross-scale synchronization
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
scale_results: Dict[str, Any]
|
|
61
|
+
total_coherence: float = 0.0
|
|
62
|
+
cross_scale_coupling: float = 0.0
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class HierarchicalTNFRNetwork:
|
|
66
|
+
"""Multi-scale TNFR network supporting operational fractality (§3.7).
|
|
67
|
+
|
|
68
|
+
Manages multiple TNFR networks at different scales with cross-scale
|
|
69
|
+
coupling, enabling simultaneous evolution while preserving structural
|
|
70
|
+
coherence.
|
|
71
|
+
|
|
72
|
+
This implementation maintains all TNFR canonical invariants:
|
|
73
|
+
- Nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
|
|
74
|
+
- Operator closure: all transformations yield valid TNFR states
|
|
75
|
+
- Phase verification: explicit synchrony checks for coupling
|
|
76
|
+
- Determinism: reproducible evolution with fixed seeds
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
scales : Sequence[ScaleDefinition]
|
|
81
|
+
Definitions of each scale in the hierarchy
|
|
82
|
+
seed : int, optional
|
|
83
|
+
Random seed for reproducible network generation
|
|
84
|
+
parallel : bool, optional
|
|
85
|
+
Enable parallel evolution of scales (default: True)
|
|
86
|
+
max_workers : int, optional
|
|
87
|
+
Maximum worker threads/processes for parallel execution
|
|
88
|
+
|
|
89
|
+
Examples
|
|
90
|
+
--------
|
|
91
|
+
Create a two-scale network and evolve it:
|
|
92
|
+
|
|
93
|
+
>>> from tnfr.multiscale import HierarchicalTNFRNetwork, ScaleDefinition
|
|
94
|
+
>>> scales = [
|
|
95
|
+
... ScaleDefinition("micro", 100, 0.8),
|
|
96
|
+
... ScaleDefinition("macro", 50, 0.5),
|
|
97
|
+
... ]
|
|
98
|
+
>>> network = HierarchicalTNFRNetwork(scales, seed=42)
|
|
99
|
+
>>> result = network.evolve_multiscale(dt=0.1, steps=10)
|
|
100
|
+
>>> result.total_coherence # doctest: +SKIP
|
|
101
|
+
0.65...
|
|
102
|
+
|
|
103
|
+
Notes
|
|
104
|
+
-----
|
|
105
|
+
Cross-scale coupling is computed as:
|
|
106
|
+
ΔNFR_total = ΔNFR_scale + Σ(coupling_ij * ΔNFR_other_scale)
|
|
107
|
+
where coupling_ij represents the strength of influence from scale j to i.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
scales: Sequence[ScaleDefinition],
|
|
113
|
+
seed: Optional[int] = None,
|
|
114
|
+
parallel: bool = True,
|
|
115
|
+
max_workers: Optional[int] = None,
|
|
116
|
+
):
|
|
117
|
+
if not scales:
|
|
118
|
+
raise ValueError("At least one scale definition required")
|
|
119
|
+
|
|
120
|
+
self.scales = list(scales)
|
|
121
|
+
self.seed = seed
|
|
122
|
+
self.parallel = parallel
|
|
123
|
+
self.max_workers = max_workers
|
|
124
|
+
|
|
125
|
+
# Initialize networks for each scale
|
|
126
|
+
self.networks_by_scale: Dict[str, TNFRGraph] = {}
|
|
127
|
+
self._initialize_scales()
|
|
128
|
+
|
|
129
|
+
# Cross-scale coupling matrix (scale x scale)
|
|
130
|
+
self.cross_scale_couplings: Dict[tuple[str, str], float] = {}
|
|
131
|
+
self._initialize_cross_scale_couplings()
|
|
132
|
+
|
|
133
|
+
logger.info(
|
|
134
|
+
f"Initialized hierarchical network with {len(scales)} scales, "
|
|
135
|
+
f"total {sum(s.node_count for s in scales)} nodes"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _initialize_scales(self) -> None:
|
|
139
|
+
"""Initialize TNFR network for each scale."""
|
|
140
|
+
rng = np.random.RandomState(self.seed)
|
|
141
|
+
|
|
142
|
+
for scale in self.scales:
|
|
143
|
+
# Create Erdős-Rényi graph for this scale
|
|
144
|
+
G = nx.erdos_renyi_graph(
|
|
145
|
+
scale.node_count, scale.edge_probability, seed=rng.randint(0, 2**31)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Initialize each node with TNFR attributes
|
|
149
|
+
for node in G.nodes():
|
|
150
|
+
node_id = f"{scale.name}_{node}"
|
|
151
|
+
G.nodes[node]["epi"] = rng.uniform(0.0, 1.0)
|
|
152
|
+
G.nodes[node]["vf"] = rng.uniform(0.5, 1.5)
|
|
153
|
+
G.nodes[node]["phase"] = rng.uniform(0.0, 2 * np.pi)
|
|
154
|
+
G.nodes[node]["delta_nfr"] = 0.0
|
|
155
|
+
G.nodes[node]["si"] = 0.0
|
|
156
|
+
|
|
157
|
+
# Set base coupling weights
|
|
158
|
+
for u, v in G.edges():
|
|
159
|
+
G[u][v]["weight"] = scale.coupling_strength * rng.uniform(0.8, 1.2)
|
|
160
|
+
|
|
161
|
+
# Install ΔNFR hook
|
|
162
|
+
set_delta_nfr_hook(G, dnfr_epi_vf_mixed)
|
|
163
|
+
|
|
164
|
+
self.networks_by_scale[scale.name] = G
|
|
165
|
+
|
|
166
|
+
def _initialize_cross_scale_couplings(self) -> None:
|
|
167
|
+
"""Initialize coupling strengths between scales.
|
|
168
|
+
|
|
169
|
+
Default: Adjacent scales couple more strongly than distant scales.
|
|
170
|
+
"""
|
|
171
|
+
scale_names = [s.name for s in self.scales]
|
|
172
|
+
n_scales = len(scale_names)
|
|
173
|
+
|
|
174
|
+
for i, scale_i in enumerate(scale_names):
|
|
175
|
+
for j, scale_j in enumerate(scale_names):
|
|
176
|
+
if i == j:
|
|
177
|
+
continue # No self-coupling
|
|
178
|
+
|
|
179
|
+
# Distance-based coupling: closer scales couple more
|
|
180
|
+
distance = abs(i - j)
|
|
181
|
+
coupling_strength = 0.3 / distance if distance > 0 else 0.0
|
|
182
|
+
|
|
183
|
+
self.cross_scale_couplings[(scale_i, scale_j)] = coupling_strength
|
|
184
|
+
|
|
185
|
+
def set_cross_scale_coupling(
|
|
186
|
+
self, from_scale: str, to_scale: str, strength: float
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Set explicit cross-scale coupling strength.
|
|
189
|
+
|
|
190
|
+
Parameters
|
|
191
|
+
----------
|
|
192
|
+
from_scale : str
|
|
193
|
+
Source scale name
|
|
194
|
+
to_scale : str
|
|
195
|
+
Target scale name
|
|
196
|
+
strength : float
|
|
197
|
+
Coupling strength (0.0 to 1.0)
|
|
198
|
+
"""
|
|
199
|
+
if from_scale not in self.networks_by_scale:
|
|
200
|
+
raise ValueError(f"Unknown scale: {from_scale}")
|
|
201
|
+
if to_scale not in self.networks_by_scale:
|
|
202
|
+
raise ValueError(f"Unknown scale: {to_scale}")
|
|
203
|
+
if strength < 0.0 or strength > 1.0:
|
|
204
|
+
raise ValueError("Coupling strength must be in [0.0, 1.0]")
|
|
205
|
+
|
|
206
|
+
self.cross_scale_couplings[(from_scale, to_scale)] = strength
|
|
207
|
+
|
|
208
|
+
def compute_multiscale_dnfr(self, node_id: NodeId, target_scale: str) -> DeltaNFR:
|
|
209
|
+
"""Compute ΔNFR considering all relevant scales.
|
|
210
|
+
|
|
211
|
+
Implements cross-scale ΔNFR computation:
|
|
212
|
+
ΔNFR_total = ΔNFR_base + Σ(coupling * ΔNFR_other)
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
node_id : NodeId
|
|
217
|
+
Node identifier within the target scale
|
|
218
|
+
target_scale : str
|
|
219
|
+
Scale where the node resides
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
DeltaNFR
|
|
224
|
+
Multi-scale ΔNFR value
|
|
225
|
+
"""
|
|
226
|
+
if target_scale not in self.networks_by_scale:
|
|
227
|
+
raise ValueError(f"Unknown scale: {target_scale}")
|
|
228
|
+
|
|
229
|
+
G = self.networks_by_scale[target_scale]
|
|
230
|
+
|
|
231
|
+
# Base ΔNFR at target scale (simplified computation)
|
|
232
|
+
base_dnfr = G.nodes[node_id].get("delta_nfr", 0.0)
|
|
233
|
+
|
|
234
|
+
# Cross-scale contributions
|
|
235
|
+
cross_scale_contribution = 0.0
|
|
236
|
+
for other_scale in self.networks_by_scale:
|
|
237
|
+
if other_scale == target_scale:
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
coupling = self.cross_scale_couplings.get((target_scale, other_scale), 0.0)
|
|
241
|
+
if coupling > 0:
|
|
242
|
+
# Aggregate ΔNFR from other scale
|
|
243
|
+
other_G = self.networks_by_scale[other_scale]
|
|
244
|
+
other_dnfr_values = [
|
|
245
|
+
other_G.nodes[n].get("delta_nfr", 0.0) for n in other_G.nodes()
|
|
246
|
+
]
|
|
247
|
+
mean_other_dnfr = (
|
|
248
|
+
np.mean(other_dnfr_values) if other_dnfr_values else 0.0
|
|
249
|
+
)
|
|
250
|
+
cross_scale_contribution += coupling * mean_other_dnfr
|
|
251
|
+
|
|
252
|
+
return base_dnfr + cross_scale_contribution
|
|
253
|
+
|
|
254
|
+
def compute_total_coherence(self) -> float:
|
|
255
|
+
"""Compute aggregated coherence across all scales.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
float
|
|
260
|
+
Total coherence C(t) aggregated across scales
|
|
261
|
+
"""
|
|
262
|
+
total_c = 0.0
|
|
263
|
+
total_nodes = 0
|
|
264
|
+
|
|
265
|
+
for scale_name, G in self.networks_by_scale.items():
|
|
266
|
+
# Simplified coherence: 1 - mean(|ΔNFR|)
|
|
267
|
+
dnfr_values = [abs(G.nodes[n].get("delta_nfr", 0.0)) for n in G.nodes()]
|
|
268
|
+
mean_abs_dnfr = np.mean(dnfr_values) if dnfr_values else 0.0
|
|
269
|
+
scale_coherence = 1.0 / (1.0 + mean_abs_dnfr)
|
|
270
|
+
|
|
271
|
+
# Weight by node count
|
|
272
|
+
node_count = G.number_of_nodes()
|
|
273
|
+
total_c += scale_coherence * node_count
|
|
274
|
+
total_nodes += node_count
|
|
275
|
+
|
|
276
|
+
return total_c / total_nodes if total_nodes > 0 else 0.0
|
|
277
|
+
|
|
278
|
+
def evolve_multiscale(
|
|
279
|
+
self,
|
|
280
|
+
dt: float = 0.1,
|
|
281
|
+
steps: int = 10,
|
|
282
|
+
operators: Optional[Sequence[str]] = None,
|
|
283
|
+
) -> EvolutionResult:
|
|
284
|
+
"""Evolve all scales simultaneously with cross-coupling.
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
dt : float
|
|
289
|
+
Time step for evolution
|
|
290
|
+
steps : int
|
|
291
|
+
Number of evolution steps
|
|
292
|
+
operators : Sequence[str], optional
|
|
293
|
+
Structural operators to apply (e.g., ["A'L", "THOL"])
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
EvolutionResult
|
|
298
|
+
Results containing scale-specific and aggregated metrics
|
|
299
|
+
"""
|
|
300
|
+
if operators is None:
|
|
301
|
+
operators = ["THOL"] # Default: Coherence operator
|
|
302
|
+
|
|
303
|
+
results = {}
|
|
304
|
+
|
|
305
|
+
for step in range(steps):
|
|
306
|
+
if self.parallel and self.max_workers != 1:
|
|
307
|
+
# Parallel evolution
|
|
308
|
+
results = self._evolve_parallel(dt, operators)
|
|
309
|
+
else:
|
|
310
|
+
# Sequential evolution
|
|
311
|
+
results = self._evolve_sequential(dt, operators)
|
|
312
|
+
|
|
313
|
+
# Apply cross-scale coupling effects
|
|
314
|
+
self._apply_cross_scale_coupling(dt)
|
|
315
|
+
|
|
316
|
+
# Compute final metrics
|
|
317
|
+
total_coherence = self.compute_total_coherence()
|
|
318
|
+
cross_coupling = self._compute_cross_scale_synchrony()
|
|
319
|
+
|
|
320
|
+
return EvolutionResult(
|
|
321
|
+
scale_results=results,
|
|
322
|
+
total_coherence=total_coherence,
|
|
323
|
+
cross_scale_coupling=cross_coupling,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def _evolve_sequential(self, dt: float, operators: Sequence[str]) -> Dict[str, Any]:
|
|
327
|
+
"""Evolve scales sequentially."""
|
|
328
|
+
results = {}
|
|
329
|
+
|
|
330
|
+
for scale_name, G in self.networks_by_scale.items():
|
|
331
|
+
# Simple evolution: update ΔNFR for all nodes
|
|
332
|
+
for node in G.nodes():
|
|
333
|
+
phase = G.nodes[node]["phase"]
|
|
334
|
+
vf = G.nodes[node]["vf"]
|
|
335
|
+
|
|
336
|
+
# Compute neighbor phase difference contribution
|
|
337
|
+
neighbors = list(G.neighbors(node))
|
|
338
|
+
if neighbors:
|
|
339
|
+
phase_diffs = [
|
|
340
|
+
np.sin(phase - G.nodes[n]["phase"]) for n in neighbors
|
|
341
|
+
]
|
|
342
|
+
dnfr = np.mean(phase_diffs)
|
|
343
|
+
else:
|
|
344
|
+
dnfr = 0.0
|
|
345
|
+
|
|
346
|
+
G.nodes[node]["delta_nfr"] = dnfr
|
|
347
|
+
|
|
348
|
+
# Update EPI according to nodal equation: ∂EPI/∂t = νf · ΔNFR
|
|
349
|
+
G.nodes[node]["epi"] += vf * dnfr * dt
|
|
350
|
+
|
|
351
|
+
results[scale_name] = {"coherence": self._scale_coherence(G)}
|
|
352
|
+
|
|
353
|
+
return results
|
|
354
|
+
|
|
355
|
+
def _evolve_parallel(self, dt: float, operators: Sequence[str]) -> Dict[str, Any]:
|
|
356
|
+
"""Evolve scales in parallel using ThreadPoolExecutor.
|
|
357
|
+
|
|
358
|
+
Note: ThreadPoolExecutor is used instead of ProcessPoolExecutor because:
|
|
359
|
+
1. NetworkX graphs are not easily picklable (required for multiprocessing)
|
|
360
|
+
2. The overhead of serializing/deserializing graphs would negate benefits
|
|
361
|
+
3. Thread-based parallelism still provides speedup for I/O and NumPy ops
|
|
362
|
+
|
|
363
|
+
For CPU-intensive workloads on very large scales, consider using
|
|
364
|
+
ProcessPoolExecutor with custom serialization or shared memory.
|
|
365
|
+
"""
|
|
366
|
+
results = {}
|
|
367
|
+
|
|
368
|
+
# Use ThreadPoolExecutor for GIL-safe parallel evolution
|
|
369
|
+
# (ProcessPoolExecutor would require pickling networkx graphs)
|
|
370
|
+
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
|
371
|
+
futures = {
|
|
372
|
+
scale_name: executor.submit(
|
|
373
|
+
self._evolve_single_scale, scale_name, dt, operators
|
|
374
|
+
)
|
|
375
|
+
for scale_name in self.networks_by_scale
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for scale_name, future in futures.items():
|
|
379
|
+
results[scale_name] = future.result()
|
|
380
|
+
|
|
381
|
+
return results
|
|
382
|
+
|
|
383
|
+
def _evolve_single_scale(
|
|
384
|
+
self, scale_name: str, dt: float, operators: Sequence[str]
|
|
385
|
+
) -> Dict[str, Any]:
|
|
386
|
+
"""Evolve a single scale (helper for parallel execution)."""
|
|
387
|
+
G = self.networks_by_scale[scale_name]
|
|
388
|
+
|
|
389
|
+
# Same logic as _evolve_sequential but for one scale
|
|
390
|
+
for node in G.nodes():
|
|
391
|
+
phase = G.nodes[node]["phase"]
|
|
392
|
+
vf = G.nodes[node]["vf"]
|
|
393
|
+
|
|
394
|
+
neighbors = list(G.neighbors(node))
|
|
395
|
+
if neighbors:
|
|
396
|
+
phase_diffs = [np.sin(phase - G.nodes[n]["phase"]) for n in neighbors]
|
|
397
|
+
dnfr = np.mean(phase_diffs)
|
|
398
|
+
else:
|
|
399
|
+
dnfr = 0.0
|
|
400
|
+
|
|
401
|
+
G.nodes[node]["delta_nfr"] = dnfr
|
|
402
|
+
G.nodes[node]["epi"] += vf * dnfr * dt
|
|
403
|
+
|
|
404
|
+
return {"coherence": self._scale_coherence(G)}
|
|
405
|
+
|
|
406
|
+
def _apply_cross_scale_coupling(self, dt: float) -> None:
|
|
407
|
+
"""Apply cross-scale coupling effects after evolution step."""
|
|
408
|
+
# For each scale, add cross-scale ΔNFR contributions
|
|
409
|
+
for target_scale in self.networks_by_scale:
|
|
410
|
+
G_target = self.networks_by_scale[target_scale]
|
|
411
|
+
|
|
412
|
+
for node in G_target.nodes():
|
|
413
|
+
cross_contribution = 0.0
|
|
414
|
+
|
|
415
|
+
for source_scale in self.networks_by_scale:
|
|
416
|
+
if source_scale == target_scale:
|
|
417
|
+
continue
|
|
418
|
+
|
|
419
|
+
coupling = self.cross_scale_couplings.get(
|
|
420
|
+
(target_scale, source_scale), 0.0
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
if coupling > 0:
|
|
424
|
+
G_source = self.networks_by_scale[source_scale]
|
|
425
|
+
source_dnfr_values = [
|
|
426
|
+
G_source.nodes[n].get("delta_nfr", 0.0)
|
|
427
|
+
for n in G_source.nodes()
|
|
428
|
+
]
|
|
429
|
+
mean_source_dnfr = (
|
|
430
|
+
np.mean(source_dnfr_values) if source_dnfr_values else 0.0
|
|
431
|
+
)
|
|
432
|
+
cross_contribution += coupling * mean_source_dnfr
|
|
433
|
+
|
|
434
|
+
# Apply cross-scale effect to EPI
|
|
435
|
+
if cross_contribution != 0.0:
|
|
436
|
+
vf = G_target.nodes[node]["vf"]
|
|
437
|
+
G_target.nodes[node]["epi"] += vf * cross_contribution * dt
|
|
438
|
+
|
|
439
|
+
def _scale_coherence(self, G: TNFRGraph) -> float:
|
|
440
|
+
"""Compute coherence for a single scale."""
|
|
441
|
+
dnfr_values = [abs(G.nodes[n].get("delta_nfr", 0.0)) for n in G.nodes()]
|
|
442
|
+
mean_abs_dnfr = np.mean(dnfr_values) if dnfr_values else 0.0
|
|
443
|
+
return 1.0 / (1.0 + mean_abs_dnfr)
|
|
444
|
+
|
|
445
|
+
def _compute_cross_scale_synchrony(self) -> float:
|
|
446
|
+
"""Compute cross-scale phase synchronization."""
|
|
447
|
+
if len(self.networks_by_scale) < 2:
|
|
448
|
+
return 0.0
|
|
449
|
+
|
|
450
|
+
# Simplified: compare mean phases across scales
|
|
451
|
+
scale_mean_phases = []
|
|
452
|
+
for G in self.networks_by_scale.values():
|
|
453
|
+
phases = [G.nodes[n]["phase"] for n in G.nodes()]
|
|
454
|
+
if phases:
|
|
455
|
+
# Use circular mean for phases
|
|
456
|
+
mean_phase = np.angle(np.mean(np.exp(1j * np.array(phases))))
|
|
457
|
+
scale_mean_phases.append(mean_phase)
|
|
458
|
+
|
|
459
|
+
if len(scale_mean_phases) < 2:
|
|
460
|
+
return 0.0
|
|
461
|
+
|
|
462
|
+
# Compute phase coherence between scales
|
|
463
|
+
phase_diffs = []
|
|
464
|
+
for i in range(len(scale_mean_phases)):
|
|
465
|
+
for j in range(i + 1, len(scale_mean_phases)):
|
|
466
|
+
phase_diff = abs(scale_mean_phases[i] - scale_mean_phases[j])
|
|
467
|
+
# Normalize to [0, π]
|
|
468
|
+
phase_diff = min(phase_diff, 2 * np.pi - phase_diff)
|
|
469
|
+
phase_diffs.append(phase_diff)
|
|
470
|
+
|
|
471
|
+
mean_phase_diff = np.mean(phase_diffs) if phase_diffs else 0.0
|
|
472
|
+
# Convert to synchrony metric (0 = no sync, 1 = perfect sync)
|
|
473
|
+
synchrony = 1.0 - (mean_phase_diff / np.pi)
|
|
474
|
+
|
|
475
|
+
return max(0.0, synchrony)
|
|
476
|
+
|
|
477
|
+
def get_scale_network(self, scale_name: str) -> TNFRGraph:
|
|
478
|
+
"""Get the network graph for a specific scale.
|
|
479
|
+
|
|
480
|
+
Parameters
|
|
481
|
+
----------
|
|
482
|
+
scale_name : str
|
|
483
|
+
Name of the scale
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
TNFRGraph
|
|
488
|
+
NetworkX graph for the specified scale
|
|
489
|
+
"""
|
|
490
|
+
if scale_name not in self.networks_by_scale:
|
|
491
|
+
raise ValueError(f"Unknown scale: {scale_name}")
|
|
492
|
+
return self.networks_by_scale[scale_name]
|
|
493
|
+
|
|
494
|
+
def memory_footprint(self) -> Dict[str, float]:
|
|
495
|
+
"""Estimate memory usage per scale.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
Dict[str, float]
|
|
500
|
+
Memory usage in MB for each scale
|
|
501
|
+
"""
|
|
502
|
+
footprint = {}
|
|
503
|
+
for scale_name, G in self.networks_by_scale.items():
|
|
504
|
+
# Rough estimate: graph structure + node attributes
|
|
505
|
+
n_nodes = G.number_of_nodes()
|
|
506
|
+
n_edges = G.number_of_edges()
|
|
507
|
+
|
|
508
|
+
# NetworkX overhead + node dict + edge dict + attributes
|
|
509
|
+
# Each node: ~200 bytes (dict overhead) + 4 attributes * 8 bytes
|
|
510
|
+
# Each edge: ~100 bytes (dict overhead) + 1 attribute * 8 bytes
|
|
511
|
+
estimate_bytes = n_nodes * 232 + n_edges * 108
|
|
512
|
+
estimate_mb = estimate_bytes / (1024 * 1024)
|
|
513
|
+
|
|
514
|
+
footprint[scale_name] = estimate_mb
|
|
515
|
+
|
|
516
|
+
footprint["total"] = sum(v for k, v in footprint.items() if k != "total")
|
|
517
|
+
return footprint
|