tnfr 3.0.3__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 +375 -56
- 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 +723 -0
- 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 +171 -0
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +110 -0
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +489 -0
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +914 -0
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +51 -0
- 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/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/config/defaults_core.py +212 -0
- 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 +92 -0
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +33 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +104 -0
- 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 +238 -0
- 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 +3034 -0
- 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 +661 -0
- 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 +36 -0
- 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 +223 -0
- 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 +262 -0
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +354 -0
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +377 -0
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +218 -0
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +203 -0
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +10 -0
- tnfr/io.pyi +13 -0
- tnfr/locking.py +37 -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 +79 -0
- 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 +2009 -0
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +158 -0
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +316 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +833 -0
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +179 -0
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +379 -0
- 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 +183 -0
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1203 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +373 -0
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +233 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +763 -0
- tnfr/node.pyi +139 -0
- tnfr/observers.py +255 -130
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +144 -137
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1672 -0
- 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 +272 -0
- 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 +1809 -0
- 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 +178 -0
- 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 +247 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +378 -0
- 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 +705 -0
- 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 +58 -0
- 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 +543 -0
- 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 +775 -0
- 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/utils/callbacks.py +375 -0
- 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/utils/numeric.py +114 -0
- 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-8.5.0.dist-info/entry_points.txt +3 -0
- tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
- tnfr/constants.py +0 -183
- tnfr/dynamics.py +0 -543
- tnfr/helpers.py +0 -198
- tnfr/main.py +0 -37
- tnfr/operators.py +0 -296
- tnfr-3.0.3.dist-info/METADATA +0 -35
- tnfr-3.0.3.dist-info/RECORD +0 -13
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/cli/execution.py
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
"""CLI execution helpers for running canonical TNFR programs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import math
|
|
7
|
+
from collections import deque
|
|
8
|
+
from collections.abc import Iterable, Mapping, Sized
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from importlib import import_module
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
import networkx as nx
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
# Constants
|
|
18
|
+
TWO_PI = 2 * math.pi
|
|
19
|
+
|
|
20
|
+
from ..config import apply_config
|
|
21
|
+
from ..config.presets import (
|
|
22
|
+
PREFERRED_PRESET_NAMES,
|
|
23
|
+
get_preset,
|
|
24
|
+
)
|
|
25
|
+
from ..alias import get_attr
|
|
26
|
+
from ..constants import METRIC_DEFAULTS, VF_PRIMARY, get_aliases, get_param
|
|
27
|
+
from ..dynamics import default_glyph_selector, parametric_glyph_selector, run
|
|
28
|
+
from ..execution import CANONICAL_PRESET_NAME, play
|
|
29
|
+
from ..flatten import parse_program_tokens
|
|
30
|
+
from ..glyph_history import ensure_history
|
|
31
|
+
from ..mathematics import (
|
|
32
|
+
BasicStateProjector,
|
|
33
|
+
CoherenceOperator,
|
|
34
|
+
FrequencyOperator,
|
|
35
|
+
HilbertSpace,
|
|
36
|
+
MathematicalDynamicsEngine,
|
|
37
|
+
make_coherence_operator,
|
|
38
|
+
make_frequency_operator,
|
|
39
|
+
)
|
|
40
|
+
from ..validation import NFRValidator
|
|
41
|
+
from ..metrics import (
|
|
42
|
+
build_metrics_summary,
|
|
43
|
+
export_metrics,
|
|
44
|
+
glyph_top,
|
|
45
|
+
register_metrics_callbacks,
|
|
46
|
+
)
|
|
47
|
+
from ..metrics.core import _metrics_step
|
|
48
|
+
from ..ontosim import prepare_network
|
|
49
|
+
from ..sense import register_sigma_callback
|
|
50
|
+
from ..trace import register_trace
|
|
51
|
+
from ..types import ProgramTokens
|
|
52
|
+
from ..utils import (
|
|
53
|
+
StructuredFileError,
|
|
54
|
+
clamp01,
|
|
55
|
+
get_logger,
|
|
56
|
+
json_dumps,
|
|
57
|
+
read_structured_file,
|
|
58
|
+
safe_write,
|
|
59
|
+
)
|
|
60
|
+
from .arguments import _args_to_dict
|
|
61
|
+
from .utils import _parse_cli_variants
|
|
62
|
+
from ..validation import validate_canon
|
|
63
|
+
|
|
64
|
+
logger = get_logger(__name__)
|
|
65
|
+
|
|
66
|
+
_VF_ALIASES = get_aliases("VF")
|
|
67
|
+
VF_ALIAS_KEYS: tuple[str, ...] = (VF_PRIMARY,) + tuple(
|
|
68
|
+
alias for alias in _VF_ALIASES if alias != VF_PRIMARY
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_EPI_ALIASES = get_aliases("EPI")
|
|
72
|
+
EPI_PRIMARY = _EPI_ALIASES[0]
|
|
73
|
+
EPI_ALIAS_KEYS: tuple[str, ...] = (EPI_PRIMARY,) + tuple(
|
|
74
|
+
alias for alias in _EPI_ALIASES if alias != EPI_PRIMARY
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# CLI summaries should remain concise by default while allowing callers to
|
|
78
|
+
# inspect the full glyphogram series when needed.
|
|
79
|
+
DEFAULT_SUMMARY_SERIES_LIMIT = 10
|
|
80
|
+
|
|
81
|
+
_PREFERRED_PRESETS_DISPLAY = ", ".join(PREFERRED_PRESET_NAMES)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _as_iterable_view(view: Any) -> Iterable[Any]:
|
|
85
|
+
"""Return ``view`` as an iterable, resolving callable cached views."""
|
|
86
|
+
|
|
87
|
+
if hasattr(view, "__iter__"):
|
|
88
|
+
return view # type: ignore[return-value]
|
|
89
|
+
if callable(view):
|
|
90
|
+
resolved = view()
|
|
91
|
+
if not hasattr(resolved, "__iter__"):
|
|
92
|
+
raise TypeError("Graph view did not return an iterable")
|
|
93
|
+
return resolved
|
|
94
|
+
return ()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _iter_graph_nodes(graph: Any) -> Iterable[Any]:
|
|
98
|
+
"""Yield nodes from ``graph`` normalising NetworkX-style accessors."""
|
|
99
|
+
|
|
100
|
+
return _as_iterable_view(getattr(graph, "nodes", ()))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _iter_graph_edges(graph: Any) -> Iterable[Any]:
|
|
104
|
+
"""Yield edges from ``graph`` normalising NetworkX-style accessors."""
|
|
105
|
+
|
|
106
|
+
return _as_iterable_view(getattr(graph, "edges", ()))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _count_graph_nodes(graph: Any) -> int:
|
|
110
|
+
"""Return node count honouring :class:`tnfr.types.GraphLike` semantics."""
|
|
111
|
+
|
|
112
|
+
if hasattr(graph, "number_of_nodes"):
|
|
113
|
+
return int(graph.number_of_nodes())
|
|
114
|
+
nodes_view = _iter_graph_nodes(graph)
|
|
115
|
+
if isinstance(nodes_view, Sized):
|
|
116
|
+
return len(nodes_view) # type: ignore[arg-type]
|
|
117
|
+
return len(tuple(nodes_view))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _save_json(path: str, data: Any) -> None:
|
|
121
|
+
payload = json_dumps(data, ensure_ascii=False, indent=2, default=list)
|
|
122
|
+
safe_write(path, lambda f: f.write(payload))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _attach_callbacks(G: "nx.Graph") -> None:
|
|
126
|
+
register_sigma_callback(G)
|
|
127
|
+
register_metrics_callbacks(G)
|
|
128
|
+
register_trace(G)
|
|
129
|
+
history = ensure_history(G)
|
|
130
|
+
maxlen = int(get_param(G, "PROGRAM_TRACE_MAXLEN"))
|
|
131
|
+
history.setdefault("program_trace", deque(maxlen=maxlen))
|
|
132
|
+
history.setdefault("trace_meta", [])
|
|
133
|
+
_metrics_step(G, ctx=None)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _persist_history(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
137
|
+
if getattr(args, "save_history", None) or getattr(
|
|
138
|
+
args, "export_history_base", None
|
|
139
|
+
):
|
|
140
|
+
history = ensure_history(G)
|
|
141
|
+
if getattr(args, "save_history", None):
|
|
142
|
+
_save_json(args.save_history, history)
|
|
143
|
+
if getattr(args, "export_history_base", None):
|
|
144
|
+
export_metrics(G, args.export_history_base, fmt=args.export_format)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _to_float_array(values: Sequence[float] | None, *, name: str) -> np.ndarray | None:
|
|
148
|
+
if values is None:
|
|
149
|
+
return None
|
|
150
|
+
array = np.asarray(list(values), dtype=float)
|
|
151
|
+
if array.ndim != 1:
|
|
152
|
+
raise ValueError(f"{name} must be a one-dimensional sequence of numbers")
|
|
153
|
+
return array
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _resolve_math_dimension(args: argparse.Namespace, fallback: int) -> int:
|
|
157
|
+
dimension = getattr(args, "math_dimension", None)
|
|
158
|
+
candidate_lengths: list[int] = []
|
|
159
|
+
for attr in (
|
|
160
|
+
"math_coherence_spectrum",
|
|
161
|
+
"math_frequency_diagonal",
|
|
162
|
+
"math_generator_diagonal",
|
|
163
|
+
):
|
|
164
|
+
seq = getattr(args, attr, None)
|
|
165
|
+
if seq is not None:
|
|
166
|
+
candidate_lengths.append(len(seq))
|
|
167
|
+
if dimension is None:
|
|
168
|
+
if candidate_lengths:
|
|
169
|
+
unique = set(candidate_lengths)
|
|
170
|
+
if len(unique) > 1:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
"Math engine configuration requires matching sequence lengths"
|
|
173
|
+
)
|
|
174
|
+
dimension = unique.pop()
|
|
175
|
+
else:
|
|
176
|
+
dimension = fallback
|
|
177
|
+
else:
|
|
178
|
+
for length in candidate_lengths:
|
|
179
|
+
if length != dimension:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Math engine sequence lengths must match the requested dimension"
|
|
182
|
+
)
|
|
183
|
+
if dimension is None or dimension <= 0:
|
|
184
|
+
raise ValueError("Hilbert space dimension must be a positive integer")
|
|
185
|
+
return int(dimension)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _build_math_engine_config(
|
|
189
|
+
G: "nx.Graph", args: argparse.Namespace
|
|
190
|
+
) -> dict[str, Any]:
|
|
191
|
+
node_count = _count_graph_nodes(G)
|
|
192
|
+
fallback_dim = max(1, int(node_count) if node_count is not None else 1)
|
|
193
|
+
dimension = _resolve_math_dimension(args, fallback=fallback_dim)
|
|
194
|
+
|
|
195
|
+
coherence_spectrum = _to_float_array(
|
|
196
|
+
getattr(args, "math_coherence_spectrum", None),
|
|
197
|
+
name="--math-coherence-spectrum",
|
|
198
|
+
)
|
|
199
|
+
if coherence_spectrum is not None and coherence_spectrum.size != dimension:
|
|
200
|
+
raise ValueError("Coherence spectrum length must equal the Hilbert dimension")
|
|
201
|
+
|
|
202
|
+
frequency_diagonal = _to_float_array(
|
|
203
|
+
getattr(args, "math_frequency_diagonal", None),
|
|
204
|
+
name="--math-frequency-diagonal",
|
|
205
|
+
)
|
|
206
|
+
if frequency_diagonal is not None and frequency_diagonal.size != dimension:
|
|
207
|
+
raise ValueError("Frequency diagonal length must equal the Hilbert dimension")
|
|
208
|
+
|
|
209
|
+
generator_diagonal = _to_float_array(
|
|
210
|
+
getattr(args, "math_generator_diagonal", None),
|
|
211
|
+
name="--math-generator-diagonal",
|
|
212
|
+
)
|
|
213
|
+
if generator_diagonal is not None and generator_diagonal.size != dimension:
|
|
214
|
+
raise ValueError("Generator diagonal length must equal the Hilbert dimension")
|
|
215
|
+
|
|
216
|
+
coherence_c_min = getattr(args, "math_coherence_c_min", None)
|
|
217
|
+
if coherence_spectrum is None:
|
|
218
|
+
coherence_operator = make_coherence_operator(
|
|
219
|
+
dimension,
|
|
220
|
+
c_min=float(coherence_c_min) if coherence_c_min is not None else 0.1,
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
if coherence_c_min is not None:
|
|
224
|
+
coherence_operator = CoherenceOperator(
|
|
225
|
+
coherence_spectrum, c_min=float(coherence_c_min)
|
|
226
|
+
)
|
|
227
|
+
else:
|
|
228
|
+
coherence_operator = CoherenceOperator(coherence_spectrum)
|
|
229
|
+
if not coherence_operator.is_positive_semidefinite():
|
|
230
|
+
raise ValueError("Coherence spectrum must be positive semidefinite")
|
|
231
|
+
|
|
232
|
+
frequency_matrix: np.ndarray
|
|
233
|
+
if frequency_diagonal is None:
|
|
234
|
+
frequency_matrix = np.eye(dimension, dtype=float)
|
|
235
|
+
else:
|
|
236
|
+
frequency_matrix = np.diag(frequency_diagonal)
|
|
237
|
+
frequency_operator = make_frequency_operator(frequency_matrix)
|
|
238
|
+
|
|
239
|
+
generator_matrix: np.ndarray
|
|
240
|
+
if generator_diagonal is None:
|
|
241
|
+
generator_matrix = np.zeros((dimension, dimension), dtype=float)
|
|
242
|
+
else:
|
|
243
|
+
generator_matrix = np.diag(generator_diagonal)
|
|
244
|
+
|
|
245
|
+
hilbert_space = HilbertSpace(dimension)
|
|
246
|
+
dynamics_engine = MathematicalDynamicsEngine(
|
|
247
|
+
generator_matrix,
|
|
248
|
+
hilbert_space=hilbert_space,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
coherence_threshold = getattr(args, "math_coherence_threshold", None)
|
|
252
|
+
if coherence_threshold is None:
|
|
253
|
+
coherence_threshold = float(coherence_operator.c_min)
|
|
254
|
+
else:
|
|
255
|
+
coherence_threshold = float(coherence_threshold)
|
|
256
|
+
|
|
257
|
+
state_projector = BasicStateProjector()
|
|
258
|
+
validator = NFRValidator(
|
|
259
|
+
hilbert_space,
|
|
260
|
+
coherence_operator,
|
|
261
|
+
coherence_threshold,
|
|
262
|
+
frequency_operator=frequency_operator,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"enabled": True,
|
|
267
|
+
"dimension": dimension,
|
|
268
|
+
"hilbert_space": hilbert_space,
|
|
269
|
+
"coherence_operator": coherence_operator,
|
|
270
|
+
"frequency_operator": frequency_operator,
|
|
271
|
+
"coherence_threshold": coherence_threshold,
|
|
272
|
+
"state_projector": state_projector,
|
|
273
|
+
"validator": validator,
|
|
274
|
+
"dynamics_engine": dynamics_engine,
|
|
275
|
+
"generator_matrix": generator_matrix,
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _configure_math_engine(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
280
|
+
if not getattr(args, "math_engine", False):
|
|
281
|
+
G.graph.pop("MATH_ENGINE", None)
|
|
282
|
+
return
|
|
283
|
+
try:
|
|
284
|
+
config = _build_math_engine_config(G, args)
|
|
285
|
+
except ValueError as exc:
|
|
286
|
+
logger.error("Math engine configuration error: %s", exc)
|
|
287
|
+
raise SystemExit(1) from exc
|
|
288
|
+
G.graph["MATH_ENGINE"] = config
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def build_basic_graph(args: argparse.Namespace) -> "nx.Graph":
|
|
292
|
+
"""Construct the base graph topology described by CLI ``args``."""
|
|
293
|
+
|
|
294
|
+
n = args.nodes
|
|
295
|
+
topology = getattr(args, "topology", "ring").lower()
|
|
296
|
+
seed = getattr(args, "seed", None)
|
|
297
|
+
if topology == "ring":
|
|
298
|
+
G = nx.cycle_graph(n)
|
|
299
|
+
elif topology == "complete":
|
|
300
|
+
G = nx.complete_graph(n)
|
|
301
|
+
elif topology == "erdos":
|
|
302
|
+
if getattr(args, "p", None) is not None:
|
|
303
|
+
prob = float(args.p)
|
|
304
|
+
else:
|
|
305
|
+
if n <= 0:
|
|
306
|
+
fallback = 0.0
|
|
307
|
+
else:
|
|
308
|
+
fallback = 3.0 / n
|
|
309
|
+
prob = clamp01(fallback)
|
|
310
|
+
if not 0.0 <= prob <= 1.0:
|
|
311
|
+
raise ValueError(f"p must be between 0 and 1; received {prob}")
|
|
312
|
+
G = nx.gnp_random_graph(n, prob, seed=seed)
|
|
313
|
+
else:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
f"Invalid topology '{topology}'. Accepted options are: ring, complete, erdos"
|
|
316
|
+
)
|
|
317
|
+
if seed is not None:
|
|
318
|
+
G.graph["RANDOM_SEED"] = int(seed)
|
|
319
|
+
return G
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
323
|
+
"""Apply CLI overrides from ``args`` to graph-level configuration."""
|
|
324
|
+
|
|
325
|
+
if args.config:
|
|
326
|
+
try:
|
|
327
|
+
apply_config(G, Path(args.config))
|
|
328
|
+
except (StructuredFileError, ValueError) as exc:
|
|
329
|
+
logger.error("%s", exc)
|
|
330
|
+
raise SystemExit(1) from exc
|
|
331
|
+
arg_map = {
|
|
332
|
+
"dt": ("DT", float),
|
|
333
|
+
"integrator": ("INTEGRATOR_METHOD", str),
|
|
334
|
+
"remesh_mode": ("REMESH_MODE", str),
|
|
335
|
+
"glyph_hysteresis_window": ("GLYPH_HYSTERESIS_WINDOW", int),
|
|
336
|
+
}
|
|
337
|
+
for attr, (key, conv) in arg_map.items():
|
|
338
|
+
val = getattr(args, attr, None)
|
|
339
|
+
if val is not None:
|
|
340
|
+
G.graph[key] = conv(val)
|
|
341
|
+
|
|
342
|
+
base_gcanon: dict[str, Any]
|
|
343
|
+
existing_gcanon = G.graph.get("GRAMMAR_CANON")
|
|
344
|
+
if isinstance(existing_gcanon, Mapping):
|
|
345
|
+
base_gcanon = {
|
|
346
|
+
**METRIC_DEFAULTS["GRAMMAR_CANON"],
|
|
347
|
+
**dict(existing_gcanon),
|
|
348
|
+
}
|
|
349
|
+
else:
|
|
350
|
+
base_gcanon = dict(METRIC_DEFAULTS["GRAMMAR_CANON"])
|
|
351
|
+
|
|
352
|
+
gcanon = {
|
|
353
|
+
**base_gcanon,
|
|
354
|
+
**_args_to_dict(args, prefix="grammar_"),
|
|
355
|
+
}
|
|
356
|
+
if getattr(args, "grammar_canon", None) is not None:
|
|
357
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
358
|
+
G.graph["GRAMMAR_CANON"] = gcanon
|
|
359
|
+
|
|
360
|
+
selector = getattr(args, "selector", None)
|
|
361
|
+
if selector is not None:
|
|
362
|
+
sel_map = {
|
|
363
|
+
"basic": default_glyph_selector,
|
|
364
|
+
"param": parametric_glyph_selector,
|
|
365
|
+
}
|
|
366
|
+
G.graph["glyph_selector"] = sel_map.get(selector, default_glyph_selector)
|
|
367
|
+
|
|
368
|
+
if hasattr(args, "gamma_type"):
|
|
369
|
+
G.graph["GAMMA"] = {
|
|
370
|
+
"type": args.gamma_type,
|
|
371
|
+
"beta": args.gamma_beta,
|
|
372
|
+
"R0": args.gamma_R0,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for attr, key in (
|
|
376
|
+
("trace_verbosity", "TRACE"),
|
|
377
|
+
("metrics_verbosity", "METRICS"),
|
|
378
|
+
):
|
|
379
|
+
cfg = G.graph.get(key)
|
|
380
|
+
if not isinstance(cfg, dict):
|
|
381
|
+
cfg = deepcopy(METRIC_DEFAULTS[key])
|
|
382
|
+
G.graph[key] = cfg
|
|
383
|
+
value = getattr(args, attr, None)
|
|
384
|
+
if value is not None:
|
|
385
|
+
cfg["verbosity"] = value
|
|
386
|
+
|
|
387
|
+
candidate_count = getattr(args, "um_candidate_count", None)
|
|
388
|
+
if candidate_count is not None:
|
|
389
|
+
G.graph["UM_CANDIDATE_COUNT"] = int(candidate_count)
|
|
390
|
+
|
|
391
|
+
stop_window = getattr(args, "stop_early_window", None)
|
|
392
|
+
stop_fraction = getattr(args, "stop_early_fraction", None)
|
|
393
|
+
if stop_window is not None or stop_fraction is not None:
|
|
394
|
+
stop_cfg = G.graph.get("STOP_EARLY")
|
|
395
|
+
if isinstance(stop_cfg, Mapping):
|
|
396
|
+
next_cfg = {**stop_cfg}
|
|
397
|
+
else:
|
|
398
|
+
next_cfg = deepcopy(METRIC_DEFAULTS["STOP_EARLY"])
|
|
399
|
+
if stop_window is not None:
|
|
400
|
+
next_cfg["window"] = int(stop_window)
|
|
401
|
+
if stop_fraction is not None:
|
|
402
|
+
next_cfg["fraction"] = float(stop_fraction)
|
|
403
|
+
next_cfg.setdefault("enabled", True)
|
|
404
|
+
G.graph["STOP_EARLY"] = next_cfg
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def register_callbacks_and_observer(G: "nx.Graph") -> None:
|
|
408
|
+
"""Attach callbacks and validators required for CLI runs."""
|
|
409
|
+
|
|
410
|
+
_attach_callbacks(G)
|
|
411
|
+
validate_canon(G)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _build_graph_from_args(args: argparse.Namespace) -> "nx.Graph":
|
|
415
|
+
G = build_basic_graph(args)
|
|
416
|
+
apply_cli_config(G, args)
|
|
417
|
+
if getattr(args, "observer", False):
|
|
418
|
+
G.graph["ATTACH_STD_OBSERVER"] = True
|
|
419
|
+
prepare_network(G)
|
|
420
|
+
register_callbacks_and_observer(G)
|
|
421
|
+
_configure_math_engine(G, args)
|
|
422
|
+
return G
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _load_sequence(path: Path) -> ProgramTokens:
|
|
426
|
+
try:
|
|
427
|
+
data = read_structured_file(path)
|
|
428
|
+
except (StructuredFileError, OSError) as exc:
|
|
429
|
+
if isinstance(exc, StructuredFileError):
|
|
430
|
+
message = str(exc)
|
|
431
|
+
else:
|
|
432
|
+
message = str(StructuredFileError(path, exc))
|
|
433
|
+
logger.error("%s", message)
|
|
434
|
+
raise SystemExit(1) from exc
|
|
435
|
+
if isinstance(data, Mapping) and "sequence" in data:
|
|
436
|
+
data = data["sequence"]
|
|
437
|
+
return parse_program_tokens(data)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def resolve_program(
|
|
441
|
+
args: argparse.Namespace, default: Optional[ProgramTokens] = None
|
|
442
|
+
) -> Optional[ProgramTokens]:
|
|
443
|
+
"""Resolve preset/sequence inputs into program tokens."""
|
|
444
|
+
|
|
445
|
+
if getattr(args, "preset", None):
|
|
446
|
+
try:
|
|
447
|
+
return get_preset(args.preset)
|
|
448
|
+
except KeyError as exc:
|
|
449
|
+
details = exc.args[0] if exc.args else "Preset lookup failed."
|
|
450
|
+
logger.error(
|
|
451
|
+
(
|
|
452
|
+
"Unknown preset '%s'. Available presets: %s. %s "
|
|
453
|
+
"Use --sequence-file to execute custom sequences."
|
|
454
|
+
),
|
|
455
|
+
args.preset,
|
|
456
|
+
_PREFERRED_PRESETS_DISPLAY,
|
|
457
|
+
details,
|
|
458
|
+
)
|
|
459
|
+
raise SystemExit(1) from exc
|
|
460
|
+
if getattr(args, "sequence_file", None):
|
|
461
|
+
return _load_sequence(Path(args.sequence_file))
|
|
462
|
+
return default
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def run_program(
|
|
466
|
+
G: Optional["nx.Graph"],
|
|
467
|
+
program: Optional[ProgramTokens],
|
|
468
|
+
args: argparse.Namespace,
|
|
469
|
+
) -> "nx.Graph":
|
|
470
|
+
"""Execute ``program`` (or timed run) on ``G`` using CLI options."""
|
|
471
|
+
|
|
472
|
+
if G is None:
|
|
473
|
+
G = _build_graph_from_args(args)
|
|
474
|
+
|
|
475
|
+
if program is None:
|
|
476
|
+
steps = getattr(args, "steps", 100)
|
|
477
|
+
steps = 100 if steps is None else int(steps)
|
|
478
|
+
if steps < 0:
|
|
479
|
+
steps = 0
|
|
480
|
+
|
|
481
|
+
run_kwargs: dict[str, Any] = {}
|
|
482
|
+
for attr in ("dt", "use_Si", "apply_glyphs"):
|
|
483
|
+
value = getattr(args, attr, None)
|
|
484
|
+
if value is not None:
|
|
485
|
+
run_kwargs[attr] = value
|
|
486
|
+
|
|
487
|
+
job_overrides: dict[str, Any] = {}
|
|
488
|
+
dnfr_jobs = getattr(args, "dnfr_n_jobs", None)
|
|
489
|
+
if dnfr_jobs is not None:
|
|
490
|
+
job_overrides["dnfr_n_jobs"] = int(dnfr_jobs)
|
|
491
|
+
if job_overrides:
|
|
492
|
+
run_kwargs["n_jobs"] = job_overrides
|
|
493
|
+
|
|
494
|
+
run(G, steps=steps, **run_kwargs)
|
|
495
|
+
else:
|
|
496
|
+
play(G, program)
|
|
497
|
+
|
|
498
|
+
_persist_history(G, args)
|
|
499
|
+
return G
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def _run_cli_program(
|
|
503
|
+
args: argparse.Namespace,
|
|
504
|
+
*,
|
|
505
|
+
default_program: Optional[ProgramTokens] = None,
|
|
506
|
+
graph: Optional["nx.Graph"] = None,
|
|
507
|
+
) -> tuple[int, Optional["nx.Graph"]]:
|
|
508
|
+
try:
|
|
509
|
+
program = resolve_program(args, default=default_program)
|
|
510
|
+
except SystemExit as exc:
|
|
511
|
+
code = exc.code if isinstance(exc.code, int) else 1
|
|
512
|
+
return code or 1, None
|
|
513
|
+
|
|
514
|
+
try:
|
|
515
|
+
result_graph = run_program(graph, program, args)
|
|
516
|
+
except SystemExit as exc:
|
|
517
|
+
code = exc.code if isinstance(exc.code, int) else 1
|
|
518
|
+
return code or 1, None
|
|
519
|
+
return 0, result_graph
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def _log_math_engine_summary(G: "nx.Graph") -> None:
|
|
523
|
+
math_cfg = G.graph.get("MATH_ENGINE")
|
|
524
|
+
if not isinstance(math_cfg, Mapping) or not math_cfg.get("enabled"):
|
|
525
|
+
return
|
|
526
|
+
|
|
527
|
+
nodes = list(G.nodes)
|
|
528
|
+
if not nodes:
|
|
529
|
+
logger.info("[MATH] Math engine validation skipped: no nodes present")
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
hilbert_space: HilbertSpace = math_cfg["hilbert_space"]
|
|
533
|
+
coherence_operator: CoherenceOperator = math_cfg["coherence_operator"]
|
|
534
|
+
frequency_operator: FrequencyOperator | None = math_cfg.get("frequency_operator")
|
|
535
|
+
state_projector: BasicStateProjector = math_cfg.get(
|
|
536
|
+
"state_projector", BasicStateProjector()
|
|
537
|
+
)
|
|
538
|
+
validator: NFRValidator | None = math_cfg.get("validator")
|
|
539
|
+
if validator is None:
|
|
540
|
+
coherence_threshold = math_cfg.get("coherence_threshold")
|
|
541
|
+
validator = NFRValidator(
|
|
542
|
+
hilbert_space,
|
|
543
|
+
coherence_operator,
|
|
544
|
+
float(coherence_threshold) if coherence_threshold is not None else 0.0,
|
|
545
|
+
frequency_operator=frequency_operator,
|
|
546
|
+
)
|
|
547
|
+
math_cfg["validator"] = validator
|
|
548
|
+
|
|
549
|
+
enforce_frequency = bool(frequency_operator is not None)
|
|
550
|
+
|
|
551
|
+
norm_values: list[float] = []
|
|
552
|
+
normalized_flags: list[bool] = []
|
|
553
|
+
coherence_flags: list[bool] = []
|
|
554
|
+
coherence_values: list[float] = []
|
|
555
|
+
coherence_threshold: float | None = None
|
|
556
|
+
frequency_flags: list[bool] = []
|
|
557
|
+
frequency_values: list[float] = []
|
|
558
|
+
frequency_spectrum_min: float | None = None
|
|
559
|
+
|
|
560
|
+
for node_id in nodes:
|
|
561
|
+
data = G.nodes[node_id]
|
|
562
|
+
epi = float(
|
|
563
|
+
get_attr(
|
|
564
|
+
data,
|
|
565
|
+
EPI_ALIAS_KEYS,
|
|
566
|
+
default=0.0,
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
nu_f = float(
|
|
570
|
+
get_attr(
|
|
571
|
+
data,
|
|
572
|
+
VF_ALIAS_KEYS,
|
|
573
|
+
default=float(data.get(VF_PRIMARY, 0.0)),
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
theta = float(data.get("theta", 0.0))
|
|
577
|
+
state = state_projector(
|
|
578
|
+
epi=epi, nu_f=nu_f, theta=theta, dim=hilbert_space.dimension
|
|
579
|
+
)
|
|
580
|
+
norm_values.append(float(hilbert_space.norm(state)))
|
|
581
|
+
outcome = validator.validate(
|
|
582
|
+
state,
|
|
583
|
+
enforce_frequency_positivity=enforce_frequency,
|
|
584
|
+
)
|
|
585
|
+
summary = outcome.summary
|
|
586
|
+
normalized_flags.append(bool(summary.get("normalized", False)))
|
|
587
|
+
|
|
588
|
+
coherence_summary = summary.get("coherence")
|
|
589
|
+
if isinstance(coherence_summary, Mapping):
|
|
590
|
+
coherence_flags.append(bool(coherence_summary.get("passed", False)))
|
|
591
|
+
coherence_values.append(float(coherence_summary.get("value", 0.0)))
|
|
592
|
+
if coherence_threshold is None and "threshold" in coherence_summary:
|
|
593
|
+
coherence_threshold = float(coherence_summary.get("threshold", 0.0))
|
|
594
|
+
|
|
595
|
+
frequency_summary = summary.get("frequency")
|
|
596
|
+
if isinstance(frequency_summary, Mapping):
|
|
597
|
+
frequency_flags.append(bool(frequency_summary.get("passed", False)))
|
|
598
|
+
frequency_values.append(float(frequency_summary.get("value", 0.0)))
|
|
599
|
+
if frequency_spectrum_min is None and "spectrum_min" in frequency_summary:
|
|
600
|
+
frequency_spectrum_min = float(
|
|
601
|
+
frequency_summary.get("spectrum_min", 0.0)
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
if norm_values:
|
|
605
|
+
logger.info(
|
|
606
|
+
"[MATH] Hilbert norm preserved=%s (min=%.6f, max=%.6f)",
|
|
607
|
+
all(normalized_flags),
|
|
608
|
+
min(norm_values),
|
|
609
|
+
max(norm_values),
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
if coherence_values and coherence_threshold is not None:
|
|
613
|
+
logger.info(
|
|
614
|
+
"[MATH] Coherence ≥ C_min=%s (C_min=%.6f, min=%.6f)",
|
|
615
|
+
all(coherence_flags),
|
|
616
|
+
float(coherence_threshold),
|
|
617
|
+
min(coherence_values),
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
if frequency_values:
|
|
621
|
+
if frequency_spectrum_min is not None:
|
|
622
|
+
logger.info(
|
|
623
|
+
"[MATH] νf positivity=%s (min=%.6f, spectrum_min=%.6f)",
|
|
624
|
+
all(frequency_flags),
|
|
625
|
+
min(frequency_values),
|
|
626
|
+
frequency_spectrum_min,
|
|
627
|
+
)
|
|
628
|
+
else:
|
|
629
|
+
logger.info(
|
|
630
|
+
"[MATH] νf positivity=%s (min=%.6f)",
|
|
631
|
+
all(frequency_flags),
|
|
632
|
+
min(frequency_values),
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def _log_run_summaries(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
637
|
+
cfg_coh = G.graph.get("COHERENCE", METRIC_DEFAULTS["COHERENCE"])
|
|
638
|
+
cfg_diag = G.graph.get("DIAGNOSIS", METRIC_DEFAULTS["DIAGNOSIS"])
|
|
639
|
+
hist = ensure_history(G)
|
|
640
|
+
|
|
641
|
+
if cfg_coh.get("enabled", True):
|
|
642
|
+
Wstats = hist.get(cfg_coh.get("stats_history_key", "W_stats"), [])
|
|
643
|
+
if Wstats:
|
|
644
|
+
logger.info("[COHERENCE] last step: %s", Wstats[-1])
|
|
645
|
+
|
|
646
|
+
if cfg_diag.get("enabled", True):
|
|
647
|
+
last_diag = hist.get(cfg_diag.get("history_key", "nodal_diag"), [])
|
|
648
|
+
if last_diag:
|
|
649
|
+
sample = list(last_diag[-1].values())[:3]
|
|
650
|
+
logger.info("[DIAGNOSIS] sample: %s", sample)
|
|
651
|
+
|
|
652
|
+
if args.summary:
|
|
653
|
+
summary_limit = getattr(args, "summary_limit", DEFAULT_SUMMARY_SERIES_LIMIT)
|
|
654
|
+
summary, has_latency_values = build_metrics_summary(
|
|
655
|
+
G, series_limit=summary_limit
|
|
656
|
+
)
|
|
657
|
+
logger.info("Global Tg: %s", summary["Tg_global"])
|
|
658
|
+
logger.info("Top operators by Tg: %s", glyph_top(G, k=5))
|
|
659
|
+
if has_latency_values:
|
|
660
|
+
logger.info("Average latency: %s", summary["latency_mean"])
|
|
661
|
+
|
|
662
|
+
_log_math_engine_summary(G)
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
666
|
+
"""Execute ``tnfr run`` returning the exit status."""
|
|
667
|
+
|
|
668
|
+
code, graph = _run_cli_program(args)
|
|
669
|
+
if code != 0:
|
|
670
|
+
return code
|
|
671
|
+
|
|
672
|
+
if graph is not None:
|
|
673
|
+
_log_run_summaries(graph, args)
|
|
674
|
+
return 0
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def cmd_sequence(args: argparse.Namespace) -> int:
|
|
678
|
+
"""Execute ``tnfr sequence`` returning the exit status."""
|
|
679
|
+
|
|
680
|
+
if args.preset and args.sequence_file:
|
|
681
|
+
logger.error("Cannot use --preset and --sequence-file at the same time")
|
|
682
|
+
return 1
|
|
683
|
+
code, _ = _run_cli_program(args, default_program=get_preset(CANONICAL_PRESET_NAME))
|
|
684
|
+
return code
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
def cmd_metrics(args: argparse.Namespace) -> int:
|
|
688
|
+
"""Execute ``tnfr metrics`` returning the exit status."""
|
|
689
|
+
|
|
690
|
+
if getattr(args, "steps", None) is None:
|
|
691
|
+
# Default a longer run for metrics stability
|
|
692
|
+
args.steps = 200
|
|
693
|
+
|
|
694
|
+
code, graph = _run_cli_program(args)
|
|
695
|
+
if code != 0 or graph is None:
|
|
696
|
+
return code
|
|
697
|
+
|
|
698
|
+
summary_limit = getattr(args, "summary_limit", None)
|
|
699
|
+
out, _ = build_metrics_summary(graph, series_limit=summary_limit)
|
|
700
|
+
if args.save:
|
|
701
|
+
_save_json(args.save, out)
|
|
702
|
+
else:
|
|
703
|
+
logger.info("%s", json_dumps(out))
|
|
704
|
+
return 0
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def cmd_profile_si(args: argparse.Namespace) -> int:
|
|
708
|
+
"""Execute ``tnfr profile-si`` returning the exit status."""
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
profile_module = import_module("benchmarks.compute_si_profile")
|
|
712
|
+
except ModuleNotFoundError as exc: # pragma: no cover - optional dependency
|
|
713
|
+
logger.error("Sense Index profiling helpers unavailable: %s", exc)
|
|
714
|
+
return 1
|
|
715
|
+
|
|
716
|
+
profile_compute_si = getattr(profile_module, "profile_compute_si")
|
|
717
|
+
|
|
718
|
+
profile_compute_si(
|
|
719
|
+
node_count=int(args.nodes),
|
|
720
|
+
chord_step=int(args.chord_step),
|
|
721
|
+
loops=int(args.loops),
|
|
722
|
+
output_dir=Path(args.output_dir),
|
|
723
|
+
fmt=str(args.format),
|
|
724
|
+
sort=str(args.sort),
|
|
725
|
+
)
|
|
726
|
+
return 0
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+
def cmd_profile_pipeline(args: argparse.Namespace) -> int:
|
|
730
|
+
"""Execute ``tnfr profile-pipeline`` returning the exit status."""
|
|
731
|
+
|
|
732
|
+
try:
|
|
733
|
+
profile_module = import_module("benchmarks.full_pipeline_profile")
|
|
734
|
+
except ModuleNotFoundError as exc: # pragma: no cover - optional dependency
|
|
735
|
+
logger.error("Full pipeline profiling helpers unavailable: %s", exc)
|
|
736
|
+
return 1
|
|
737
|
+
|
|
738
|
+
profile_full_pipeline = getattr(profile_module, "profile_full_pipeline")
|
|
739
|
+
|
|
740
|
+
try:
|
|
741
|
+
si_chunk_sizes = _parse_cli_variants(getattr(args, "si_chunk_sizes", None))
|
|
742
|
+
dnfr_chunk_sizes = _parse_cli_variants(getattr(args, "dnfr_chunk_sizes", None))
|
|
743
|
+
si_workers = _parse_cli_variants(getattr(args, "si_workers", None))
|
|
744
|
+
dnfr_workers = _parse_cli_variants(getattr(args, "dnfr_workers", None))
|
|
745
|
+
except ValueError as exc:
|
|
746
|
+
logger.error("%s", exc)
|
|
747
|
+
return 2
|
|
748
|
+
|
|
749
|
+
profile_full_pipeline(
|
|
750
|
+
node_count=int(args.nodes),
|
|
751
|
+
edge_probability=float(args.edge_probability),
|
|
752
|
+
loops=int(args.loops),
|
|
753
|
+
seed=int(args.seed),
|
|
754
|
+
output_dir=Path(args.output_dir),
|
|
755
|
+
sort=str(args.sort),
|
|
756
|
+
si_chunk_sizes=si_chunk_sizes,
|
|
757
|
+
dnfr_chunk_sizes=dnfr_chunk_sizes,
|
|
758
|
+
si_workers=si_workers,
|
|
759
|
+
dnfr_workers=dnfr_workers,
|
|
760
|
+
)
|
|
761
|
+
return 0
|
|
762
|
+
|
|
763
|
+
|
|
764
|
+
def cmd_math_run(args: argparse.Namespace) -> int:
|
|
765
|
+
"""Execute ``tnfr math.run`` returning the exit status.
|
|
766
|
+
|
|
767
|
+
This command always enables the mathematical dynamics engine for
|
|
768
|
+
validation of TNFR structural invariants on Hilbert space.
|
|
769
|
+
"""
|
|
770
|
+
|
|
771
|
+
# Force math engine to be enabled
|
|
772
|
+
setattr(args, "math_engine", True)
|
|
773
|
+
|
|
774
|
+
# Set default attributes if not present
|
|
775
|
+
if not hasattr(args, "summary"):
|
|
776
|
+
setattr(args, "summary", False)
|
|
777
|
+
if not hasattr(args, "summary_limit"):
|
|
778
|
+
setattr(args, "summary_limit", DEFAULT_SUMMARY_SERIES_LIMIT)
|
|
779
|
+
|
|
780
|
+
code, graph = _run_cli_program(args)
|
|
781
|
+
if code != 0:
|
|
782
|
+
return code
|
|
783
|
+
|
|
784
|
+
if graph is not None:
|
|
785
|
+
_log_run_summaries(graph, args)
|
|
786
|
+
logger.info("[MATH.RUN] Mathematical dynamics validation completed")
|
|
787
|
+
return 0
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def cmd_epi_validate(args: argparse.Namespace) -> int:
|
|
791
|
+
"""Execute ``tnfr epi.validate`` returning the exit status.
|
|
792
|
+
|
|
793
|
+
This command validates EPI structural integrity, coherence preservation,
|
|
794
|
+
and operator closure according to TNFR canonical invariants.
|
|
795
|
+
"""
|
|
796
|
+
|
|
797
|
+
code, graph = _run_cli_program(args)
|
|
798
|
+
if code != 0:
|
|
799
|
+
return code
|
|
800
|
+
|
|
801
|
+
if graph is None:
|
|
802
|
+
logger.error("[EPI.VALIDATE] No graph generated for validation")
|
|
803
|
+
return 1
|
|
804
|
+
|
|
805
|
+
# Validation checks
|
|
806
|
+
tolerance = getattr(args, "tolerance", 1e-6)
|
|
807
|
+
check_coherence = getattr(args, "check_coherence", True)
|
|
808
|
+
check_frequency = getattr(args, "check_frequency", True)
|
|
809
|
+
check_phase = getattr(args, "check_phase", True)
|
|
810
|
+
|
|
811
|
+
validation_passed = True
|
|
812
|
+
validation_summary = []
|
|
813
|
+
|
|
814
|
+
# Check coherence preservation
|
|
815
|
+
if check_coherence:
|
|
816
|
+
hist = ensure_history(graph)
|
|
817
|
+
cfg_coh = graph.graph.get("COHERENCE", METRIC_DEFAULTS["COHERENCE"])
|
|
818
|
+
if cfg_coh.get("enabled", True):
|
|
819
|
+
Wstats = hist.get(cfg_coh.get("stats_history_key", "W_stats"), [])
|
|
820
|
+
if Wstats:
|
|
821
|
+
# Check that coherence is non-negative and bounded
|
|
822
|
+
for i, stats in enumerate(Wstats):
|
|
823
|
+
W_mean = float(stats.get("mean", 0.0))
|
|
824
|
+
if W_mean < -tolerance:
|
|
825
|
+
validation_passed = False
|
|
826
|
+
validation_summary.append(
|
|
827
|
+
f" [FAIL] Step {i}: Coherence W_mean={W_mean:.6f} < 0"
|
|
828
|
+
)
|
|
829
|
+
if validation_passed:
|
|
830
|
+
validation_summary.append(
|
|
831
|
+
f" [PASS] Coherence preserved (W_mean ≥ 0 across {len(Wstats)} steps)"
|
|
832
|
+
)
|
|
833
|
+
else:
|
|
834
|
+
validation_summary.append(" [SKIP] No coherence history available")
|
|
835
|
+
else:
|
|
836
|
+
validation_summary.append(" [SKIP] Coherence tracking disabled")
|
|
837
|
+
|
|
838
|
+
# Check structural frequency positivity
|
|
839
|
+
if check_frequency:
|
|
840
|
+
nodes = list(_iter_graph_nodes(graph))
|
|
841
|
+
if nodes:
|
|
842
|
+
negative_frequencies = []
|
|
843
|
+
for node_id in nodes:
|
|
844
|
+
data = graph.nodes[node_id]
|
|
845
|
+
nu_f = float(
|
|
846
|
+
get_attr(
|
|
847
|
+
data,
|
|
848
|
+
VF_ALIAS_KEYS,
|
|
849
|
+
default=float(data.get(VF_PRIMARY, 0.0)),
|
|
850
|
+
)
|
|
851
|
+
)
|
|
852
|
+
if nu_f < -tolerance:
|
|
853
|
+
negative_frequencies.append((node_id, nu_f))
|
|
854
|
+
|
|
855
|
+
if negative_frequencies:
|
|
856
|
+
validation_passed = False
|
|
857
|
+
for node_id, nu_f in negative_frequencies[:5]: # Show first 5
|
|
858
|
+
validation_summary.append(
|
|
859
|
+
f" [FAIL] Node {node_id}: νf={nu_f:.6f} < 0"
|
|
860
|
+
)
|
|
861
|
+
if len(negative_frequencies) > 5:
|
|
862
|
+
validation_summary.append(
|
|
863
|
+
f" ... and {len(negative_frequencies) - 5} more nodes"
|
|
864
|
+
)
|
|
865
|
+
else:
|
|
866
|
+
validation_summary.append(
|
|
867
|
+
f" [PASS] Structural frequency νf ≥ 0 for all {len(nodes)} nodes"
|
|
868
|
+
)
|
|
869
|
+
else:
|
|
870
|
+
validation_summary.append(" [SKIP] No nodes to validate")
|
|
871
|
+
|
|
872
|
+
# Check phase synchrony in couplings
|
|
873
|
+
if check_phase:
|
|
874
|
+
edges = list(_iter_graph_edges(graph))
|
|
875
|
+
if edges:
|
|
876
|
+
phase_violations = []
|
|
877
|
+
for u, v in edges:
|
|
878
|
+
theta_u = float(graph.nodes[u].get("theta", 0.0))
|
|
879
|
+
theta_v = float(graph.nodes[v].get("theta", 0.0))
|
|
880
|
+
# Check if phases are defined (not both zero)
|
|
881
|
+
if abs(theta_u) > tolerance or abs(theta_v) > tolerance:
|
|
882
|
+
# Phase difference should be bounded
|
|
883
|
+
phase_diff = abs(theta_u - theta_v)
|
|
884
|
+
if phase_diff > TWO_PI: # > 2π
|
|
885
|
+
phase_violations.append((u, v, phase_diff))
|
|
886
|
+
|
|
887
|
+
if phase_violations:
|
|
888
|
+
validation_passed = False
|
|
889
|
+
for u, v, diff in phase_violations[:5]:
|
|
890
|
+
validation_summary.append(
|
|
891
|
+
f" [WARN] Edge ({u},{v}): phase diff={diff:.6f} > 2π"
|
|
892
|
+
)
|
|
893
|
+
if len(phase_violations) > 5:
|
|
894
|
+
validation_summary.append(
|
|
895
|
+
f" ... and {len(phase_violations) - 5} more edges"
|
|
896
|
+
)
|
|
897
|
+
else:
|
|
898
|
+
validation_summary.append(
|
|
899
|
+
f" [PASS] Phase synchrony maintained across {len(edges)} edges"
|
|
900
|
+
)
|
|
901
|
+
else:
|
|
902
|
+
validation_summary.append(" [SKIP] No edges to validate")
|
|
903
|
+
|
|
904
|
+
# Log validation results
|
|
905
|
+
logger.info("[EPI.VALIDATE] Validation Summary:")
|
|
906
|
+
for line in validation_summary:
|
|
907
|
+
logger.info("%s", line)
|
|
908
|
+
|
|
909
|
+
if validation_passed:
|
|
910
|
+
logger.info("[EPI.VALIDATE] ✓ All validation checks passed")
|
|
911
|
+
return 0
|
|
912
|
+
else:
|
|
913
|
+
logger.info("[EPI.VALIDATE] ✗ Some validation checks failed")
|
|
914
|
+
return 1
|