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/sdk/fluent.py
ADDED
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
"""Fluent API for simplified TNFR network creation and simulation.
|
|
2
|
+
|
|
3
|
+
This module implements the core :class:`TNFRNetwork` class that provides
|
|
4
|
+
a user-friendly, chainable interface for working with TNFR networks. The
|
|
5
|
+
API hides low-level complexity while maintaining full theoretical fidelity
|
|
6
|
+
to TNFR invariants and structural operators.
|
|
7
|
+
|
|
8
|
+
Examples
|
|
9
|
+
--------
|
|
10
|
+
Create and simulate a simple TNFR network:
|
|
11
|
+
|
|
12
|
+
>>> network = TNFRNetwork("my_experiment")
|
|
13
|
+
>>> network.add_nodes(10).connect_nodes(0.3, "random")
|
|
14
|
+
>>> network.apply_sequence("basic_activation", repeat=5)
|
|
15
|
+
>>> results = network.measure()
|
|
16
|
+
>>> print(results.summary())
|
|
17
|
+
|
|
18
|
+
Chain operations for rapid prototyping:
|
|
19
|
+
|
|
20
|
+
>>> results = (TNFRNetwork("quick_test")
|
|
21
|
+
... .add_nodes(20)
|
|
22
|
+
... .connect_nodes(0.4, "ring")
|
|
23
|
+
... .apply_sequence("network_sync", repeat=10)
|
|
24
|
+
... .measure())
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any, Dict, List, Optional, Union
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import numpy as np
|
|
35
|
+
|
|
36
|
+
_HAS_NUMPY = True
|
|
37
|
+
except ImportError:
|
|
38
|
+
np = None # type: ignore[assignment]
|
|
39
|
+
_HAS_NUMPY = False
|
|
40
|
+
|
|
41
|
+
import networkx as nx
|
|
42
|
+
|
|
43
|
+
from ..structural import create_nfr, run_sequence
|
|
44
|
+
from ..metrics.coherence import compute_coherence
|
|
45
|
+
from ..metrics.sense_index import compute_Si, compute_Si_node
|
|
46
|
+
from ..constants.aliases import ALIAS_DNFR
|
|
47
|
+
from ..validation import validate_sequence
|
|
48
|
+
|
|
49
|
+
__all__ = ["TNFRNetwork", "NetworkConfig", "NetworkResults"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Predefined operator sequences for common patterns (optimized with Grammar 2.0)
|
|
53
|
+
# All sequences must respect TNFR grammar rules:
|
|
54
|
+
# - Start with emission or recursivity
|
|
55
|
+
# - Include reception→coherence segment
|
|
56
|
+
# - Include coupling/dissonance/resonance segment
|
|
57
|
+
# - End with recursivity, silence, or transition
|
|
58
|
+
#
|
|
59
|
+
# Sequences optimized for structural health ≥ 0.7 using Grammar 2.0:
|
|
60
|
+
# - Balanced stabilizers/destabilizers
|
|
61
|
+
# - Harmonic frequency transitions
|
|
62
|
+
# - Proper closure operators
|
|
63
|
+
# - Pattern completeness
|
|
64
|
+
NAMED_SEQUENCES = {
|
|
65
|
+
# Basic activation pattern - optimized with expansion for balance
|
|
66
|
+
# Health: 0.79 (good) - Pattern: activation
|
|
67
|
+
# Includes controlled expansion for structural balance
|
|
68
|
+
"basic_activation": [
|
|
69
|
+
"emission", # AL: Initiate coherent structure
|
|
70
|
+
"reception", # EN: Stabilize incoming energy
|
|
71
|
+
"coherence", # IL: Primary stabilization (required)
|
|
72
|
+
"expansion", # VAL: Controlled growth (balance +0.33)
|
|
73
|
+
"resonance", # RA: Amplify coherent structure
|
|
74
|
+
"silence", # SHA: Sustainable pause state
|
|
75
|
+
],
|
|
76
|
+
# Stabilization with expansion - optimized for regenerative cycles
|
|
77
|
+
# Health: 0.76 (good) - Pattern: regenerative
|
|
78
|
+
# Enables recursive consolidation with controlled expansion
|
|
79
|
+
"stabilization": [
|
|
80
|
+
"emission", # AL: Initiate structure
|
|
81
|
+
"reception", # EN: Gather information
|
|
82
|
+
"coherence", # IL: Stabilize
|
|
83
|
+
"expansion", # VAL: Controlled growth (balance +0.50)
|
|
84
|
+
"resonance", # RA: Amplify through network
|
|
85
|
+
"recursivity", # REMESH: Enable fractal recursion
|
|
86
|
+
],
|
|
87
|
+
# Creative mutation - already optimal (health: 0.81)
|
|
88
|
+
# Pattern: activation with controlled transformation
|
|
89
|
+
"creative_mutation": [
|
|
90
|
+
"emission", # AL: Initiate exploration
|
|
91
|
+
"dissonance", # OZ: Introduce creative tension
|
|
92
|
+
"reception", # EN: Gather alternatives
|
|
93
|
+
"coherence", # IL: Stabilize insights
|
|
94
|
+
"mutation", # ZHIR: Phase transformation
|
|
95
|
+
"resonance", # RA: Amplify new patterns
|
|
96
|
+
"silence", # SHA: Integration pause
|
|
97
|
+
],
|
|
98
|
+
# Network synchronization - optimized with transition for regenerative capability
|
|
99
|
+
# Health: 0.77 (good) - Pattern: regenerative
|
|
100
|
+
# Enables phase synchronization across multi-node networks with dissonance for balance
|
|
101
|
+
"network_sync": [
|
|
102
|
+
"emission", # AL: Initiate network activity
|
|
103
|
+
"reception", # EN: Gather network state
|
|
104
|
+
"coherence", # IL: Stabilize local structure
|
|
105
|
+
"coupling", # UM: Establish phase synchronization
|
|
106
|
+
"resonance", # RA: Propagate through network
|
|
107
|
+
"transition", # NAV: Enable regenerative cycles (changed from silence)
|
|
108
|
+
],
|
|
109
|
+
# Exploration - already excellent (health: 0.87)
|
|
110
|
+
# Pattern: regenerative with transformative potential
|
|
111
|
+
"exploration": [
|
|
112
|
+
"emission", # AL: Begin exploration
|
|
113
|
+
"dissonance", # OZ: Introduce instability
|
|
114
|
+
"reception", # EN: Sense environment
|
|
115
|
+
"coherence", # IL: Find stable attractor
|
|
116
|
+
"resonance", # RA: Reinforce discovery
|
|
117
|
+
"transition", # NAV: Navigate to new state
|
|
118
|
+
],
|
|
119
|
+
# Consolidation - optimized with expansion for structural balance
|
|
120
|
+
# Health: 0.80 (good) - Pattern: stabilization
|
|
121
|
+
# Recursive consolidation with controlled expansion
|
|
122
|
+
"consolidation": [
|
|
123
|
+
"recursivity", # REMESH: Start from fractal structure
|
|
124
|
+
"reception", # EN: Gather current state
|
|
125
|
+
"coherence", # IL: Consolidate structure
|
|
126
|
+
"expansion", # VAL: Controlled growth (balance +0.25)
|
|
127
|
+
"resonance", # RA: Amplify consolidated state
|
|
128
|
+
"coherence", # IL: Re-stabilize after expansion
|
|
129
|
+
"silence", # SHA: Sustained stable state
|
|
130
|
+
],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass
|
|
135
|
+
class NetworkConfig:
|
|
136
|
+
"""Configuration for TNFR network creation.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
random_seed : int, optional
|
|
141
|
+
Seed for random number generation to ensure reproducibility.
|
|
142
|
+
validate_invariants : bool, default=True
|
|
143
|
+
Whether to validate TNFR invariants after operator application.
|
|
144
|
+
auto_stabilization : bool, default=True
|
|
145
|
+
Whether to automatically apply stabilization after mutations.
|
|
146
|
+
default_vf_range : tuple[float, float], default=(0.1, 1.0)
|
|
147
|
+
Default range for structural frequency (νf) generation in Hz_str.
|
|
148
|
+
default_epi_range : tuple[float, float], default=(0.1, 0.9)
|
|
149
|
+
Default range for EPI (Primary Information Structure) generation.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
random_seed: Optional[int] = None
|
|
153
|
+
validate_invariants: bool = True
|
|
154
|
+
auto_stabilization: bool = True
|
|
155
|
+
default_vf_range: tuple[float, float] = (0.1, 1.0)
|
|
156
|
+
default_epi_range: tuple[float, float] = (0.1, 0.9)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class NetworkResults:
|
|
161
|
+
"""Results from TNFR network simulation.
|
|
162
|
+
|
|
163
|
+
Attributes
|
|
164
|
+
----------
|
|
165
|
+
coherence : float
|
|
166
|
+
Global coherence C(t) of the network.
|
|
167
|
+
sense_indices : Dict[str, float]
|
|
168
|
+
Sense index Si for each node, measuring stable reorganization capacity.
|
|
169
|
+
delta_nfr : Dict[str, float]
|
|
170
|
+
Internal reorganization gradient ΔNFR for each node.
|
|
171
|
+
graph : TNFRGraph
|
|
172
|
+
The underlying NetworkX graph with full TNFR state.
|
|
173
|
+
avg_vf : float, optional
|
|
174
|
+
Average structural frequency across all nodes.
|
|
175
|
+
avg_phase : float, optional
|
|
176
|
+
Average phase angle across all nodes.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
coherence: float
|
|
180
|
+
sense_indices: Dict[str, float]
|
|
181
|
+
delta_nfr: Dict[str, float]
|
|
182
|
+
graph: Any # TNFRGraph
|
|
183
|
+
avg_vf: Optional[float] = None
|
|
184
|
+
avg_phase: Optional[float] = None
|
|
185
|
+
|
|
186
|
+
def summary(self) -> str:
|
|
187
|
+
"""Generate human-readable summary of network results.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
str
|
|
192
|
+
Formatted summary string with key metrics.
|
|
193
|
+
"""
|
|
194
|
+
si_values = list(self.sense_indices.values())
|
|
195
|
+
dnfr_values = list(self.delta_nfr.values())
|
|
196
|
+
|
|
197
|
+
avg_si = sum(si_values) / len(si_values) if si_values else 0.0
|
|
198
|
+
avg_dnfr = sum(dnfr_values) / len(dnfr_values) if dnfr_values else 0.0
|
|
199
|
+
|
|
200
|
+
return f"""
|
|
201
|
+
TNFR Network Results:
|
|
202
|
+
• Coherence C(t): {self.coherence:.3f}
|
|
203
|
+
• Nodes: {len(self.sense_indices)}
|
|
204
|
+
• Avg Sense Index Si: {avg_si:.3f}
|
|
205
|
+
• Avg ΔNFR: {avg_dnfr:.3f}
|
|
206
|
+
• Avg νf: {self.avg_vf:.3f} Hz_str{' (computed)' if self.avg_vf else ''}
|
|
207
|
+
• Avg Phase: {self.avg_phase:.3f} rad{' (computed)' if self.avg_phase else ''}
|
|
208
|
+
""".strip()
|
|
209
|
+
|
|
210
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
211
|
+
"""Convert results to serializable dictionary.
|
|
212
|
+
|
|
213
|
+
Returns
|
|
214
|
+
-------
|
|
215
|
+
Dict[str, Any]
|
|
216
|
+
Dictionary containing all metrics and summary statistics.
|
|
217
|
+
"""
|
|
218
|
+
si_values = list(self.sense_indices.values())
|
|
219
|
+
dnfr_values = list(self.delta_nfr.values())
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
"coherence": self.coherence,
|
|
223
|
+
"sense_indices": self.sense_indices,
|
|
224
|
+
"delta_nfr": self.delta_nfr,
|
|
225
|
+
"summary_stats": {
|
|
226
|
+
"node_count": len(self.sense_indices),
|
|
227
|
+
"avg_si": sum(si_values) / len(si_values) if si_values else 0.0,
|
|
228
|
+
"avg_delta_nfr": (
|
|
229
|
+
sum(dnfr_values) / len(dnfr_values) if dnfr_values else 0.0
|
|
230
|
+
),
|
|
231
|
+
"avg_vf": self.avg_vf,
|
|
232
|
+
"avg_phase": self.avg_phase,
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TNFRNetwork:
|
|
238
|
+
"""Fluent API for creating and simulating TNFR networks.
|
|
239
|
+
|
|
240
|
+
This class provides a simplified, chainable interface for working with
|
|
241
|
+
TNFR networks. All methods return ``self`` to enable method chaining.
|
|
242
|
+
The API automatically handles node creation, operator validation, and
|
|
243
|
+
metric computation while preserving TNFR structural invariants.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
name : str, default="tnfr_network"
|
|
248
|
+
Name identifier for the network.
|
|
249
|
+
config : NetworkConfig, optional
|
|
250
|
+
Configuration settings. If None, uses default configuration.
|
|
251
|
+
|
|
252
|
+
Examples
|
|
253
|
+
--------
|
|
254
|
+
Create a network with fluent interface:
|
|
255
|
+
|
|
256
|
+
>>> network = TNFRNetwork("experiment_1")
|
|
257
|
+
>>> network.add_nodes(15).connect_nodes(0.25, "random")
|
|
258
|
+
>>> network.apply_sequence("basic_activation", repeat=3)
|
|
259
|
+
>>> results = network.measure()
|
|
260
|
+
|
|
261
|
+
One-line network creation and simulation:
|
|
262
|
+
|
|
263
|
+
>>> results = (TNFRNetwork("test")
|
|
264
|
+
... .add_nodes(10)
|
|
265
|
+
... .connect_nodes(0.3)
|
|
266
|
+
... .apply_sequence("network_sync", repeat=5)
|
|
267
|
+
... .measure())
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(
|
|
271
|
+
self, name: str = "tnfr_network", config: Optional[NetworkConfig] = None
|
|
272
|
+
):
|
|
273
|
+
"""Initialize TNFR network with given name and configuration."""
|
|
274
|
+
self.name = name
|
|
275
|
+
self._config = config if config is not None else NetworkConfig()
|
|
276
|
+
self._graph: Optional[nx.Graph] = None
|
|
277
|
+
self._results: Optional[NetworkResults] = None
|
|
278
|
+
self._node_counter = 0
|
|
279
|
+
|
|
280
|
+
# Initialize RNG if seed provided
|
|
281
|
+
if _HAS_NUMPY and self._config.random_seed is not None:
|
|
282
|
+
self._rng = np.random.RandomState(self._config.random_seed)
|
|
283
|
+
else:
|
|
284
|
+
import random
|
|
285
|
+
|
|
286
|
+
if self._config.random_seed is not None:
|
|
287
|
+
random.seed(self._config.random_seed)
|
|
288
|
+
self._rng = random # type: ignore[assignment]
|
|
289
|
+
|
|
290
|
+
def add_nodes(
|
|
291
|
+
self,
|
|
292
|
+
count: int,
|
|
293
|
+
vf_range: Optional[tuple[float, float]] = None,
|
|
294
|
+
epi_range: Optional[tuple[float, float]] = None,
|
|
295
|
+
phase_range: tuple[float, float] = (0.0, 6.283185307179586), # (0, 2π)
|
|
296
|
+
random_seed: Optional[int] = None,
|
|
297
|
+
) -> TNFRNetwork:
|
|
298
|
+
"""Add nodes with random TNFR properties within valid ranges.
|
|
299
|
+
|
|
300
|
+
Creates ``count`` new nodes with structural properties (EPI, νf, phase)
|
|
301
|
+
randomly sampled within specified ranges. All values respect TNFR
|
|
302
|
+
invariants and canonical units (νf in Hz_str, phase in radians).
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
count : int
|
|
307
|
+
Number of nodes to create.
|
|
308
|
+
vf_range : tuple[float, float], optional
|
|
309
|
+
Range for structural frequency νf in Hz_str. If None, uses
|
|
310
|
+
config default. Values should be positive.
|
|
311
|
+
epi_range : tuple[float, float], optional
|
|
312
|
+
Range for Primary Information Structure (EPI). If None, uses
|
|
313
|
+
config default. Typical range is (0.1, 0.9) to avoid extremes.
|
|
314
|
+
phase_range : tuple[float, float], default=(0, 2π)
|
|
315
|
+
Range for phase in radians. Default covers full circle.
|
|
316
|
+
random_seed : int, optional
|
|
317
|
+
Override seed for this operation only. If None, uses network seed.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
TNFRNetwork
|
|
322
|
+
Self for method chaining.
|
|
323
|
+
|
|
324
|
+
Examples
|
|
325
|
+
--------
|
|
326
|
+
Add nodes with default properties:
|
|
327
|
+
|
|
328
|
+
>>> network = TNFRNetwork().add_nodes(5)
|
|
329
|
+
|
|
330
|
+
Add nodes with custom frequency range:
|
|
331
|
+
|
|
332
|
+
>>> network = TNFRNetwork().add_nodes(10, vf_range=(0.5, 2.0))
|
|
333
|
+
"""
|
|
334
|
+
if self._graph is None:
|
|
335
|
+
self._graph = nx.Graph()
|
|
336
|
+
|
|
337
|
+
if vf_range is None:
|
|
338
|
+
vf_range = self._config.default_vf_range
|
|
339
|
+
if epi_range is None:
|
|
340
|
+
epi_range = self._config.default_epi_range
|
|
341
|
+
|
|
342
|
+
# Setup RNG for this operation
|
|
343
|
+
if _HAS_NUMPY:
|
|
344
|
+
rng = (
|
|
345
|
+
np.random.RandomState(random_seed)
|
|
346
|
+
if random_seed is not None
|
|
347
|
+
else self._rng
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
for _ in range(count):
|
|
351
|
+
node_id = f"node_{self._node_counter}"
|
|
352
|
+
self._node_counter += 1
|
|
353
|
+
|
|
354
|
+
# Generate valid TNFR properties
|
|
355
|
+
vf = rng.uniform(*vf_range)
|
|
356
|
+
phase = rng.uniform(*phase_range)
|
|
357
|
+
epi = rng.uniform(*epi_range)
|
|
358
|
+
|
|
359
|
+
# Create NFR node with structural properties
|
|
360
|
+
self._graph, _ = create_nfr(
|
|
361
|
+
node_id,
|
|
362
|
+
graph=self._graph,
|
|
363
|
+
vf=vf,
|
|
364
|
+
theta=phase,
|
|
365
|
+
epi=epi,
|
|
366
|
+
)
|
|
367
|
+
else:
|
|
368
|
+
# Fallback to standard random
|
|
369
|
+
import random
|
|
370
|
+
|
|
371
|
+
if random_seed is not None:
|
|
372
|
+
random.seed(random_seed)
|
|
373
|
+
|
|
374
|
+
for _ in range(count):
|
|
375
|
+
node_id = f"node_{self._node_counter}"
|
|
376
|
+
self._node_counter += 1
|
|
377
|
+
|
|
378
|
+
vf = random.uniform(*vf_range)
|
|
379
|
+
phase = random.uniform(*phase_range)
|
|
380
|
+
epi = random.uniform(*epi_range)
|
|
381
|
+
|
|
382
|
+
self._graph, _ = create_nfr(
|
|
383
|
+
node_id,
|
|
384
|
+
graph=self._graph,
|
|
385
|
+
vf=vf,
|
|
386
|
+
theta=phase,
|
|
387
|
+
epi=epi,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return self
|
|
391
|
+
|
|
392
|
+
def connect_nodes(
|
|
393
|
+
self,
|
|
394
|
+
connection_probability: float = 0.3,
|
|
395
|
+
connection_pattern: str = "random",
|
|
396
|
+
) -> TNFRNetwork:
|
|
397
|
+
"""Connect nodes according to specified topology pattern.
|
|
398
|
+
|
|
399
|
+
Establishes coupling between nodes using common network patterns.
|
|
400
|
+
Connections enable resonance and phase synchronization between nodes.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
connection_probability : float, default=0.3
|
|
405
|
+
For random pattern: probability of edge between any two nodes.
|
|
406
|
+
For small_world: rewiring probability (Watts-Strogatz model).
|
|
407
|
+
connection_pattern : str, default="random"
|
|
408
|
+
Topology pattern to use. Options:
|
|
409
|
+
- "random": Erdős-Rényi random graph
|
|
410
|
+
- "ring": Each node connects to next in circle
|
|
411
|
+
- "small_world": Watts-Strogatz small-world network
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
TNFRNetwork
|
|
416
|
+
Self for method chaining.
|
|
417
|
+
|
|
418
|
+
Raises
|
|
419
|
+
------
|
|
420
|
+
ValueError
|
|
421
|
+
If graph has no nodes or invalid pattern specified.
|
|
422
|
+
|
|
423
|
+
Examples
|
|
424
|
+
--------
|
|
425
|
+
Create random network:
|
|
426
|
+
|
|
427
|
+
>>> network = TNFRNetwork().add_nodes(10).connect_nodes(0.3, "random")
|
|
428
|
+
|
|
429
|
+
Create ring lattice:
|
|
430
|
+
|
|
431
|
+
>>> network = TNFRNetwork().add_nodes(15).connect_nodes(pattern="ring")
|
|
432
|
+
|
|
433
|
+
Create small-world network:
|
|
434
|
+
|
|
435
|
+
>>> network = TNFRNetwork().add_nodes(20).connect_nodes(0.1, "small_world")
|
|
436
|
+
"""
|
|
437
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
438
|
+
raise ValueError("No nodes in graph. Call add_nodes() first.")
|
|
439
|
+
|
|
440
|
+
nodes = list(self._graph.nodes())
|
|
441
|
+
|
|
442
|
+
if connection_pattern == "random":
|
|
443
|
+
# Erdős-Rényi random graph
|
|
444
|
+
if _HAS_NUMPY:
|
|
445
|
+
for i, node1 in enumerate(nodes):
|
|
446
|
+
for node2 in nodes[i + 1 :]:
|
|
447
|
+
if self._rng.random() < connection_probability:
|
|
448
|
+
self._graph.add_edge(node1, node2)
|
|
449
|
+
else:
|
|
450
|
+
import random
|
|
451
|
+
|
|
452
|
+
for i, node1 in enumerate(nodes):
|
|
453
|
+
for node2 in nodes[i + 1 :]:
|
|
454
|
+
if random.random() < connection_probability:
|
|
455
|
+
self._graph.add_edge(node1, node2)
|
|
456
|
+
|
|
457
|
+
elif connection_pattern == "ring":
|
|
458
|
+
# Ring lattice
|
|
459
|
+
for i in range(len(nodes)):
|
|
460
|
+
next_node = nodes[(i + 1) % len(nodes)]
|
|
461
|
+
self._graph.add_edge(nodes[i], next_node)
|
|
462
|
+
|
|
463
|
+
elif connection_pattern == "small_world":
|
|
464
|
+
# Watts-Strogatz small-world network
|
|
465
|
+
# Start with ring lattice, then rewire
|
|
466
|
+
k = max(4, int(len(nodes) * 0.1)) # ~10% degree, minimum 4
|
|
467
|
+
|
|
468
|
+
# Create initial ring with k nearest neighbors
|
|
469
|
+
for i in range(len(nodes)):
|
|
470
|
+
for j in range(1, k // 2 + 1):
|
|
471
|
+
target = (i + j) % len(nodes)
|
|
472
|
+
if nodes[i] != nodes[target]: # Avoid self-loops
|
|
473
|
+
self._graph.add_edge(nodes[i], nodes[target])
|
|
474
|
+
|
|
475
|
+
# Rewire edges with given probability
|
|
476
|
+
edges = list(self._graph.edges())
|
|
477
|
+
if _HAS_NUMPY:
|
|
478
|
+
for u, v in edges:
|
|
479
|
+
if self._rng.random() < connection_probability:
|
|
480
|
+
# Remove edge and create new random edge
|
|
481
|
+
self._graph.remove_edge(u, v)
|
|
482
|
+
# Find node not already connected
|
|
483
|
+
candidates = [
|
|
484
|
+
n
|
|
485
|
+
for n in nodes
|
|
486
|
+
if n != u and not self._graph.has_edge(u, n)
|
|
487
|
+
]
|
|
488
|
+
if candidates:
|
|
489
|
+
idx = int(self._rng.randint(0, len(candidates)))
|
|
490
|
+
if idx >= len(candidates):
|
|
491
|
+
idx = len(candidates) - 1
|
|
492
|
+
w = candidates[idx]
|
|
493
|
+
self._graph.add_edge(u, w)
|
|
494
|
+
else:
|
|
495
|
+
import random
|
|
496
|
+
|
|
497
|
+
for u, v in edges:
|
|
498
|
+
if random.random() < connection_probability:
|
|
499
|
+
self._graph.remove_edge(u, v)
|
|
500
|
+
candidates = [
|
|
501
|
+
n
|
|
502
|
+
for n in nodes
|
|
503
|
+
if n != u and not self._graph.has_edge(u, n)
|
|
504
|
+
]
|
|
505
|
+
if candidates:
|
|
506
|
+
w = random.choice(candidates)
|
|
507
|
+
self._graph.add_edge(u, w)
|
|
508
|
+
|
|
509
|
+
else:
|
|
510
|
+
available = ", ".join(["random", "ring", "small_world"])
|
|
511
|
+
raise ValueError(
|
|
512
|
+
f"Unknown connection pattern '{connection_pattern}'. "
|
|
513
|
+
f"Available: {available}"
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
return self
|
|
517
|
+
|
|
518
|
+
def apply_sequence(
|
|
519
|
+
self,
|
|
520
|
+
sequence: Union[str, List[str]],
|
|
521
|
+
repeat: int = 1,
|
|
522
|
+
) -> TNFRNetwork:
|
|
523
|
+
"""Apply structural operator sequence to evolve the network.
|
|
524
|
+
|
|
525
|
+
Executes a validated sequence of TNFR operators that reorganize
|
|
526
|
+
network structure according to the nodal equation ∂EPI/∂t = νf·ΔNFR(t).
|
|
527
|
+
Sequences can be predefined names or custom operator lists. The
|
|
528
|
+
sequence is applied to all nodes in the network.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
sequence : str or List[str]
|
|
533
|
+
Either a predefined sequence name or list of operator names.
|
|
534
|
+
Predefined sequences:
|
|
535
|
+
- "basic_activation": [emission, reception, coherence, resonance, silence]
|
|
536
|
+
- "stabilization": [emission, reception, coherence, resonance, recursivity]
|
|
537
|
+
- "creative_mutation": [emission, dissonance, reception, coherence, mutation, resonance, silence]
|
|
538
|
+
- "network_sync": [emission, reception, coherence, coupling, resonance, silence]
|
|
539
|
+
- "exploration": [emission, dissonance, reception, coherence, resonance, transition]
|
|
540
|
+
- "consolidation": [recursivity, reception, coherence, resonance, silence]
|
|
541
|
+
repeat : int, default=1
|
|
542
|
+
Number of times to apply the sequence.
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
TNFRNetwork
|
|
547
|
+
Self for method chaining.
|
|
548
|
+
|
|
549
|
+
Raises
|
|
550
|
+
------
|
|
551
|
+
ValueError
|
|
552
|
+
If graph has no nodes or sequence name is invalid.
|
|
553
|
+
|
|
554
|
+
Examples
|
|
555
|
+
--------
|
|
556
|
+
Apply predefined sequence:
|
|
557
|
+
|
|
558
|
+
>>> network = (TNFRNetwork()
|
|
559
|
+
... .add_nodes(10)
|
|
560
|
+
... .connect_nodes(0.3)
|
|
561
|
+
... .apply_sequence("basic_activation", repeat=5))
|
|
562
|
+
|
|
563
|
+
Apply custom operator sequence:
|
|
564
|
+
|
|
565
|
+
>>> network.apply_sequence(["emission", "reception", "coherence", "resonance", "silence"])
|
|
566
|
+
"""
|
|
567
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
568
|
+
raise ValueError("No nodes in graph. Call add_nodes() first.")
|
|
569
|
+
|
|
570
|
+
# Expand named sequences
|
|
571
|
+
if isinstance(sequence, str):
|
|
572
|
+
if sequence not in NAMED_SEQUENCES:
|
|
573
|
+
available = ", ".join(sorted(NAMED_SEQUENCES.keys()))
|
|
574
|
+
raise ValueError(
|
|
575
|
+
f"Unknown sequence '{sequence}'. Available: {available}"
|
|
576
|
+
)
|
|
577
|
+
operator_list = NAMED_SEQUENCES[sequence]
|
|
578
|
+
else:
|
|
579
|
+
operator_list = sequence
|
|
580
|
+
|
|
581
|
+
# Validate sequence if configured
|
|
582
|
+
if self._config.validate_invariants:
|
|
583
|
+
validate_sequence(operator_list)
|
|
584
|
+
|
|
585
|
+
# Convert operator names to operator instances
|
|
586
|
+
from ..operators.registry import get_operator_class
|
|
587
|
+
|
|
588
|
+
operator_instances = [get_operator_class(name)() for name in operator_list]
|
|
589
|
+
|
|
590
|
+
# Apply sequence repeatedly to all nodes
|
|
591
|
+
for _ in range(repeat):
|
|
592
|
+
for node in list(self._graph.nodes()):
|
|
593
|
+
run_sequence(self._graph, node, operator_instances)
|
|
594
|
+
|
|
595
|
+
return self
|
|
596
|
+
|
|
597
|
+
def measure(self) -> NetworkResults:
|
|
598
|
+
"""Calculate TNFR metrics and return structured results.
|
|
599
|
+
|
|
600
|
+
Computes coherence C(t), sense indices Si, and ΔNFR values for
|
|
601
|
+
all nodes, plus aggregate statistics. Results are cached internally
|
|
602
|
+
and returned as a :class:`NetworkResults` instance.
|
|
603
|
+
|
|
604
|
+
Returns
|
|
605
|
+
-------
|
|
606
|
+
NetworkResults
|
|
607
|
+
Structured container with all computed metrics.
|
|
608
|
+
|
|
609
|
+
Raises
|
|
610
|
+
------
|
|
611
|
+
ValueError
|
|
612
|
+
If no network has been created.
|
|
613
|
+
|
|
614
|
+
Examples
|
|
615
|
+
--------
|
|
616
|
+
Measure and display results:
|
|
617
|
+
|
|
618
|
+
>>> results = network.measure()
|
|
619
|
+
>>> print(results.summary())
|
|
620
|
+
|
|
621
|
+
Access specific metrics:
|
|
622
|
+
|
|
623
|
+
>>> coherence = results.coherence
|
|
624
|
+
>>> si_values = results.sense_indices
|
|
625
|
+
"""
|
|
626
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
627
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
628
|
+
|
|
629
|
+
# Compute coherence C(t)
|
|
630
|
+
coherence = compute_coherence(self._graph)
|
|
631
|
+
|
|
632
|
+
# Compute sense indices Si for all nodes
|
|
633
|
+
si_dict = compute_Si(self._graph, inplace=False)
|
|
634
|
+
|
|
635
|
+
# Extract ΔNFR values
|
|
636
|
+
delta_nfr_dict = {}
|
|
637
|
+
for node_id in self._graph.nodes():
|
|
638
|
+
delta_nfr_dict[node_id] = self._graph.nodes[node_id].get(ALIAS_DNFR, 0.0)
|
|
639
|
+
|
|
640
|
+
# Compute aggregate statistics
|
|
641
|
+
vf_sum = 0.0
|
|
642
|
+
phase_sum = 0.0
|
|
643
|
+
node_count = self._graph.number_of_nodes()
|
|
644
|
+
|
|
645
|
+
for node_id in self._graph.nodes():
|
|
646
|
+
node_data = self._graph.nodes[node_id]
|
|
647
|
+
vf_sum += node_data.get("nu_f", 0.0)
|
|
648
|
+
phase_sum += node_data.get("phase", 0.0)
|
|
649
|
+
|
|
650
|
+
avg_vf = vf_sum / node_count if node_count > 0 else 0.0
|
|
651
|
+
avg_phase = phase_sum / node_count if node_count > 0 else 0.0
|
|
652
|
+
|
|
653
|
+
# Create and cache results
|
|
654
|
+
self._results = NetworkResults(
|
|
655
|
+
coherence=coherence,
|
|
656
|
+
sense_indices=si_dict,
|
|
657
|
+
delta_nfr=delta_nfr_dict,
|
|
658
|
+
graph=self._graph,
|
|
659
|
+
avg_vf=avg_vf,
|
|
660
|
+
avg_phase=avg_phase,
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
return self._results
|
|
664
|
+
|
|
665
|
+
def apply_canonical_sequence(
|
|
666
|
+
self,
|
|
667
|
+
sequence_name: str,
|
|
668
|
+
node: Optional[int] = None,
|
|
669
|
+
collect_metrics: bool = True,
|
|
670
|
+
) -> TNFRNetwork:
|
|
671
|
+
"""Apply a canonical predefined operator sequence from TNFR theory.
|
|
672
|
+
|
|
673
|
+
Executes one of the 6 archetypal sequences involving OZ (Dissonance)
|
|
674
|
+
from "El pulso que nos atraviesa" (Table 2.5). These sequences represent
|
|
675
|
+
validated structural patterns with documented use cases and domain contexts.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
sequence_name : str
|
|
680
|
+
Name of canonical sequence. Available sequences:
|
|
681
|
+
- 'bifurcated_base': OZ → ZHIR (mutation path)
|
|
682
|
+
- 'bifurcated_collapse': OZ → NUL (collapse path)
|
|
683
|
+
- 'therapeutic_protocol': Complete healing cycle
|
|
684
|
+
- 'theory_system': Epistemological construction
|
|
685
|
+
- 'full_deployment': Complete reorganization trajectory
|
|
686
|
+
- 'mod_stabilizer': OZ → ZHIR → IL (reusable macro)
|
|
687
|
+
node : int, optional
|
|
688
|
+
Target node ID. If None, applies to the most recently added node.
|
|
689
|
+
collect_metrics : bool, default=True
|
|
690
|
+
Whether to collect detailed operator metrics during execution.
|
|
691
|
+
|
|
692
|
+
Returns
|
|
693
|
+
-------
|
|
694
|
+
TNFRNetwork
|
|
695
|
+
Self for method chaining.
|
|
696
|
+
|
|
697
|
+
Raises
|
|
698
|
+
------
|
|
699
|
+
ValueError
|
|
700
|
+
If sequence_name is not recognized or network has no nodes.
|
|
701
|
+
|
|
702
|
+
Examples
|
|
703
|
+
--------
|
|
704
|
+
Apply therapeutic protocol:
|
|
705
|
+
|
|
706
|
+
>>> net = TNFRNetwork("therapy_session")
|
|
707
|
+
>>> net.add_nodes(1).apply_canonical_sequence("therapeutic_protocol")
|
|
708
|
+
>>> results = net.measure()
|
|
709
|
+
>>> print(f"Coherence: {results.coherence:.3f}")
|
|
710
|
+
|
|
711
|
+
Apply MOD_STABILIZER as reusable transformation module:
|
|
712
|
+
|
|
713
|
+
>>> net = TNFRNetwork("modular")
|
|
714
|
+
>>> net.add_nodes(1)
|
|
715
|
+
>>> net.apply_canonical_sequence("mod_stabilizer").measure()
|
|
716
|
+
|
|
717
|
+
See Also
|
|
718
|
+
--------
|
|
719
|
+
list_canonical_sequences : List available sequences with filters
|
|
720
|
+
apply_sequence : Apply predefined or custom operator sequences
|
|
721
|
+
|
|
722
|
+
Notes
|
|
723
|
+
-----
|
|
724
|
+
Canonical sequences are archetypal patterns from TNFR theory documented
|
|
725
|
+
in "El pulso que nos atraviesa", Tabla 2.5. Each sequence has been
|
|
726
|
+
validated for structural coherence and grammar compliance.
|
|
727
|
+
"""
|
|
728
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
729
|
+
raise ValueError("No nodes in graph. Call add_nodes() first.")
|
|
730
|
+
|
|
731
|
+
# Import canonical sequences registry
|
|
732
|
+
from ..operators.canonical_patterns import CANONICAL_SEQUENCES
|
|
733
|
+
|
|
734
|
+
if sequence_name not in CANONICAL_SEQUENCES:
|
|
735
|
+
available = ", ".join(sorted(CANONICAL_SEQUENCES.keys()))
|
|
736
|
+
raise ValueError(
|
|
737
|
+
f"Unknown canonical sequence '{sequence_name}'. "
|
|
738
|
+
f"Available: {available}"
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
sequence = CANONICAL_SEQUENCES[sequence_name]
|
|
742
|
+
|
|
743
|
+
# Determine target node
|
|
744
|
+
if node is None:
|
|
745
|
+
# Use last added node
|
|
746
|
+
nodes_list = list(self._graph.nodes())
|
|
747
|
+
target_node = nodes_list[-1] if nodes_list else 0
|
|
748
|
+
else:
|
|
749
|
+
target_node = node
|
|
750
|
+
if target_node not in self._graph.nodes():
|
|
751
|
+
raise ValueError(f"Node {target_node} not found in network")
|
|
752
|
+
|
|
753
|
+
# Configure metrics collection
|
|
754
|
+
self._graph.graph["COLLECT_OPERATOR_METRICS"] = collect_metrics
|
|
755
|
+
|
|
756
|
+
# Map glyphs to operator instances
|
|
757
|
+
from ..operators.definitions import (
|
|
758
|
+
Emission,
|
|
759
|
+
Reception,
|
|
760
|
+
Coherence,
|
|
761
|
+
Dissonance,
|
|
762
|
+
Coupling,
|
|
763
|
+
Resonance,
|
|
764
|
+
Silence,
|
|
765
|
+
Expansion,
|
|
766
|
+
Contraction,
|
|
767
|
+
SelfOrganization,
|
|
768
|
+
Mutation,
|
|
769
|
+
Transition,
|
|
770
|
+
Recursivity,
|
|
771
|
+
)
|
|
772
|
+
from ..types import Glyph
|
|
773
|
+
|
|
774
|
+
glyph_to_operator = {
|
|
775
|
+
Glyph.AL: Emission(),
|
|
776
|
+
Glyph.EN: Reception(),
|
|
777
|
+
Glyph.IL: Coherence(),
|
|
778
|
+
Glyph.OZ: Dissonance(),
|
|
779
|
+
Glyph.UM: Coupling(),
|
|
780
|
+
Glyph.RA: Resonance(),
|
|
781
|
+
Glyph.SHA: Silence(),
|
|
782
|
+
Glyph.VAL: Expansion(),
|
|
783
|
+
Glyph.NUL: Contraction(),
|
|
784
|
+
Glyph.THOL: SelfOrganization(),
|
|
785
|
+
Glyph.ZHIR: Mutation(),
|
|
786
|
+
Glyph.NAV: Transition(),
|
|
787
|
+
Glyph.REMESH: Recursivity(),
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
operators = [glyph_to_operator[g] for g in sequence.glyphs]
|
|
791
|
+
run_sequence(self._graph, target_node, operators)
|
|
792
|
+
|
|
793
|
+
return self
|
|
794
|
+
|
|
795
|
+
def list_canonical_sequences(
|
|
796
|
+
self,
|
|
797
|
+
domain: Optional[str] = None,
|
|
798
|
+
with_oz: bool = False,
|
|
799
|
+
) -> Dict[str, Any]:
|
|
800
|
+
"""List available canonical sequences with optional filters.
|
|
801
|
+
|
|
802
|
+
Returns a dictionary of canonical operator sequences from TNFR theory.
|
|
803
|
+
Sequences can be filtered by domain or by presence of OZ (Dissonance).
|
|
804
|
+
|
|
805
|
+
Parameters
|
|
806
|
+
----------
|
|
807
|
+
domain : str, optional
|
|
808
|
+
Filter by domain. Options:
|
|
809
|
+
- 'general': Cross-domain patterns
|
|
810
|
+
- 'biomedical': Therapeutic and healing sequences
|
|
811
|
+
- 'cognitive': Epistemological and learning patterns
|
|
812
|
+
- 'social': Organizational and collective sequences
|
|
813
|
+
with_oz : bool, default=False
|
|
814
|
+
If True, only return sequences containing OZ (Dissonance) operator.
|
|
815
|
+
|
|
816
|
+
Returns
|
|
817
|
+
-------
|
|
818
|
+
dict
|
|
819
|
+
Dictionary mapping sequence names to CanonicalSequence objects.
|
|
820
|
+
Each entry contains: name, glyphs, pattern_type, description,
|
|
821
|
+
use_cases, domain, and references.
|
|
822
|
+
|
|
823
|
+
Examples
|
|
824
|
+
--------
|
|
825
|
+
List all canonical sequences:
|
|
826
|
+
|
|
827
|
+
>>> net = TNFRNetwork("explorer")
|
|
828
|
+
>>> sequences = net.list_canonical_sequences()
|
|
829
|
+
>>> for name in sequences:
|
|
830
|
+
... print(name)
|
|
831
|
+
bifurcated_base
|
|
832
|
+
bifurcated_collapse
|
|
833
|
+
therapeutic_protocol
|
|
834
|
+
theory_system
|
|
835
|
+
full_deployment
|
|
836
|
+
mod_stabilizer
|
|
837
|
+
|
|
838
|
+
List only sequences with OZ:
|
|
839
|
+
|
|
840
|
+
>>> oz_sequences = net.list_canonical_sequences(with_oz=True)
|
|
841
|
+
>>> print(f"Found {len(oz_sequences)} sequences with OZ")
|
|
842
|
+
Found 6 sequences with OZ
|
|
843
|
+
|
|
844
|
+
List biomedical domain sequences:
|
|
845
|
+
|
|
846
|
+
>>> bio_sequences = net.list_canonical_sequences(domain="biomedical")
|
|
847
|
+
>>> for name, seq in bio_sequences.items():
|
|
848
|
+
... print(f"{name}: {seq.description[:50]}...")
|
|
849
|
+
|
|
850
|
+
See Also
|
|
851
|
+
--------
|
|
852
|
+
apply_canonical_sequence : Apply a canonical sequence to the network
|
|
853
|
+
"""
|
|
854
|
+
from ..operators.canonical_patterns import CANONICAL_SEQUENCES
|
|
855
|
+
from ..types import Glyph
|
|
856
|
+
|
|
857
|
+
sequences = CANONICAL_SEQUENCES.copy()
|
|
858
|
+
|
|
859
|
+
# Filter by domain if specified
|
|
860
|
+
if domain is not None:
|
|
861
|
+
sequences = {
|
|
862
|
+
name: seq for name, seq in sequences.items() if seq.domain == domain
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
# Filter by OZ presence if requested
|
|
866
|
+
if with_oz:
|
|
867
|
+
sequences = {
|
|
868
|
+
name: seq for name, seq in sequences.items() if Glyph.OZ in seq.glyphs
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return sequences
|
|
872
|
+
|
|
873
|
+
def visualize(self, **kwargs: Any) -> TNFRNetwork:
|
|
874
|
+
"""Visualize the network with TNFR metrics.
|
|
875
|
+
|
|
876
|
+
Creates a visual representation of the network showing node states
|
|
877
|
+
and connections. Requires matplotlib to be installed.
|
|
878
|
+
|
|
879
|
+
Parameters
|
|
880
|
+
----------
|
|
881
|
+
**kwargs
|
|
882
|
+
Additional arguments passed to visualization function.
|
|
883
|
+
|
|
884
|
+
Returns
|
|
885
|
+
-------
|
|
886
|
+
TNFRNetwork
|
|
887
|
+
Self for method chaining.
|
|
888
|
+
|
|
889
|
+
Raises
|
|
890
|
+
------
|
|
891
|
+
ImportError
|
|
892
|
+
If matplotlib is not installed.
|
|
893
|
+
ValueError
|
|
894
|
+
If no network has been created.
|
|
895
|
+
|
|
896
|
+
Notes
|
|
897
|
+
-----
|
|
898
|
+
This is a placeholder for future visualization functionality.
|
|
899
|
+
Current implementation will raise NotImplementedError.
|
|
900
|
+
"""
|
|
901
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
902
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
903
|
+
|
|
904
|
+
# Compute metrics if not done yet
|
|
905
|
+
if self._results is None:
|
|
906
|
+
self.measure()
|
|
907
|
+
|
|
908
|
+
# Visualization will be implemented in future PR
|
|
909
|
+
raise NotImplementedError(
|
|
910
|
+
"Visualization functionality will be added in a future update. "
|
|
911
|
+
"Use NetworkX's drawing functions directly on network._graph for now."
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
def save(self, filepath: Union[str, Path]) -> TNFRNetwork:
|
|
915
|
+
"""Save network state and results to file.
|
|
916
|
+
|
|
917
|
+
Serializes the network graph and computed metrics to a file for
|
|
918
|
+
later analysis or reproduction.
|
|
919
|
+
|
|
920
|
+
Parameters
|
|
921
|
+
----------
|
|
922
|
+
filepath : str or Path
|
|
923
|
+
Path where network data should be saved.
|
|
924
|
+
|
|
925
|
+
Returns
|
|
926
|
+
-------
|
|
927
|
+
TNFRNetwork
|
|
928
|
+
Self for method chaining.
|
|
929
|
+
|
|
930
|
+
Raises
|
|
931
|
+
------
|
|
932
|
+
ValueError
|
|
933
|
+
If no network has been created.
|
|
934
|
+
|
|
935
|
+
Notes
|
|
936
|
+
-----
|
|
937
|
+
This is a placeholder for future I/O functionality.
|
|
938
|
+
Current implementation will raise NotImplementedError.
|
|
939
|
+
"""
|
|
940
|
+
if self._graph is None or self._graph.number_of_nodes() == 0:
|
|
941
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
942
|
+
|
|
943
|
+
# Compute metrics if not done yet
|
|
944
|
+
if self._results is None:
|
|
945
|
+
self.measure()
|
|
946
|
+
|
|
947
|
+
# I/O functionality will be implemented in future PR
|
|
948
|
+
raise NotImplementedError(
|
|
949
|
+
"Save functionality will be added in a future update. "
|
|
950
|
+
"Use networkx.write_gpickle or similar for now."
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
@property
|
|
954
|
+
def graph(self) -> nx.Graph:
|
|
955
|
+
"""Access the underlying NetworkX graph.
|
|
956
|
+
|
|
957
|
+
Returns
|
|
958
|
+
-------
|
|
959
|
+
nx.Graph
|
|
960
|
+
The NetworkX graph with TNFR node attributes.
|
|
961
|
+
|
|
962
|
+
Raises
|
|
963
|
+
------
|
|
964
|
+
ValueError
|
|
965
|
+
If no network has been created yet.
|
|
966
|
+
"""
|
|
967
|
+
if self._graph is None:
|
|
968
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
969
|
+
return self._graph
|
|
970
|
+
|
|
971
|
+
def get_node_count(self) -> int:
|
|
972
|
+
"""Get the number of nodes in the network.
|
|
973
|
+
|
|
974
|
+
Returns
|
|
975
|
+
-------
|
|
976
|
+
int
|
|
977
|
+
Number of nodes.
|
|
978
|
+
|
|
979
|
+
Raises
|
|
980
|
+
------
|
|
981
|
+
ValueError
|
|
982
|
+
If no network has been created.
|
|
983
|
+
"""
|
|
984
|
+
if self._graph is None:
|
|
985
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
986
|
+
return self._graph.number_of_nodes()
|
|
987
|
+
|
|
988
|
+
def get_edge_count(self) -> int:
|
|
989
|
+
"""Get the number of edges in the network.
|
|
990
|
+
|
|
991
|
+
Returns
|
|
992
|
+
-------
|
|
993
|
+
int
|
|
994
|
+
Number of edges.
|
|
995
|
+
|
|
996
|
+
Raises
|
|
997
|
+
------
|
|
998
|
+
ValueError
|
|
999
|
+
If no network has been created.
|
|
1000
|
+
"""
|
|
1001
|
+
if self._graph is None:
|
|
1002
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
1003
|
+
return self._graph.number_of_edges()
|
|
1004
|
+
|
|
1005
|
+
def get_average_degree(self) -> float:
|
|
1006
|
+
"""Get the average degree of nodes in the network.
|
|
1007
|
+
|
|
1008
|
+
Returns
|
|
1009
|
+
-------
|
|
1010
|
+
float
|
|
1011
|
+
Average node degree.
|
|
1012
|
+
|
|
1013
|
+
Raises
|
|
1014
|
+
------
|
|
1015
|
+
ValueError
|
|
1016
|
+
If no network has been created.
|
|
1017
|
+
"""
|
|
1018
|
+
if self._graph is None:
|
|
1019
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
1020
|
+
if self._graph.number_of_nodes() == 0:
|
|
1021
|
+
return 0.0
|
|
1022
|
+
return 2.0 * self._graph.number_of_edges() / self._graph.number_of_nodes()
|
|
1023
|
+
|
|
1024
|
+
def get_density(self) -> float:
|
|
1025
|
+
"""Get the density of the network.
|
|
1026
|
+
|
|
1027
|
+
Network density is the ratio of actual edges to possible edges.
|
|
1028
|
+
|
|
1029
|
+
Returns
|
|
1030
|
+
-------
|
|
1031
|
+
float
|
|
1032
|
+
Network density between 0 and 1.
|
|
1033
|
+
|
|
1034
|
+
Raises
|
|
1035
|
+
------
|
|
1036
|
+
ValueError
|
|
1037
|
+
If no network has been created.
|
|
1038
|
+
"""
|
|
1039
|
+
if self._graph is None:
|
|
1040
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
1041
|
+
n = self._graph.number_of_nodes()
|
|
1042
|
+
if n <= 1:
|
|
1043
|
+
return 0.0
|
|
1044
|
+
m = self._graph.number_of_edges()
|
|
1045
|
+
max_edges = n * (n - 1) / 2
|
|
1046
|
+
return m / max_edges if max_edges > 0 else 0.0
|
|
1047
|
+
|
|
1048
|
+
def clone(self) -> TNFRNetwork:
|
|
1049
|
+
"""Create a copy of the network structure.
|
|
1050
|
+
|
|
1051
|
+
Returns
|
|
1052
|
+
-------
|
|
1053
|
+
TNFRNetwork
|
|
1054
|
+
A new network with copied structure. Note that this copies
|
|
1055
|
+
the graph structure but not all internal state (like locks).
|
|
1056
|
+
|
|
1057
|
+
Raises
|
|
1058
|
+
------
|
|
1059
|
+
ValueError
|
|
1060
|
+
If no network has been created.
|
|
1061
|
+
"""
|
|
1062
|
+
if self._graph is None:
|
|
1063
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
1064
|
+
|
|
1065
|
+
import networkx as nx
|
|
1066
|
+
|
|
1067
|
+
new_network = TNFRNetwork(f"{self.name}_copy", config=self._config)
|
|
1068
|
+
# Use NetworkX's copy method which handles TNFR graphs properly
|
|
1069
|
+
new_network._graph = nx.Graph(self._graph)
|
|
1070
|
+
new_network._node_counter = self._node_counter
|
|
1071
|
+
return new_network
|
|
1072
|
+
|
|
1073
|
+
def reset(self) -> TNFRNetwork:
|
|
1074
|
+
"""Reset the network to empty state.
|
|
1075
|
+
|
|
1076
|
+
Returns
|
|
1077
|
+
-------
|
|
1078
|
+
TNFRNetwork
|
|
1079
|
+
Self for method chaining.
|
|
1080
|
+
"""
|
|
1081
|
+
self._graph = None
|
|
1082
|
+
self._results = None
|
|
1083
|
+
self._node_counter = 0
|
|
1084
|
+
return self
|
|
1085
|
+
|
|
1086
|
+
def export_to_dict(self) -> dict:
|
|
1087
|
+
"""Export network structure to dictionary format.
|
|
1088
|
+
|
|
1089
|
+
Returns
|
|
1090
|
+
-------
|
|
1091
|
+
dict
|
|
1092
|
+
Dictionary with network metadata and structure.
|
|
1093
|
+
|
|
1094
|
+
Raises
|
|
1095
|
+
------
|
|
1096
|
+
ValueError
|
|
1097
|
+
If no network has been created.
|
|
1098
|
+
"""
|
|
1099
|
+
if self._graph is None:
|
|
1100
|
+
raise ValueError("No network created. Use add_nodes() first.")
|
|
1101
|
+
|
|
1102
|
+
# Measure if not done yet
|
|
1103
|
+
if self._results is None:
|
|
1104
|
+
self.measure()
|
|
1105
|
+
|
|
1106
|
+
return {
|
|
1107
|
+
"name": self.name,
|
|
1108
|
+
"metadata": {
|
|
1109
|
+
"nodes": self.get_node_count(),
|
|
1110
|
+
"edges": self.get_edge_count(),
|
|
1111
|
+
"density": self.get_density(),
|
|
1112
|
+
"average_degree": self.get_average_degree(),
|
|
1113
|
+
},
|
|
1114
|
+
"metrics": self._results.to_dict() if self._results else None,
|
|
1115
|
+
"config": {
|
|
1116
|
+
"random_seed": self._config.random_seed,
|
|
1117
|
+
"validate_invariants": self._config.validate_invariants,
|
|
1118
|
+
"vf_range": self._config.default_vf_range,
|
|
1119
|
+
"epi_range": self._config.default_epi_range,
|
|
1120
|
+
},
|
|
1121
|
+
}
|