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/cli/execution.py
CHANGED
|
@@ -1,48 +1,121 @@
|
|
|
1
|
+
"""CLI execution helpers for running canonical TNFR programs."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
3
5
|
import argparse
|
|
4
|
-
|
|
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
|
|
5
11
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Optional
|
|
12
|
+
from typing import Any, Optional, Sequence
|
|
7
13
|
|
|
8
|
-
import networkx as nx
|
|
14
|
+
import networkx as nx
|
|
15
|
+
import numpy as np
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
12
41
|
from ..metrics import (
|
|
13
|
-
register_metrics_callbacks,
|
|
14
|
-
glyph_top,
|
|
15
|
-
export_metrics,
|
|
16
42
|
build_metrics_summary,
|
|
43
|
+
export_metrics,
|
|
44
|
+
glyph_top,
|
|
45
|
+
register_metrics_callbacks,
|
|
17
46
|
)
|
|
18
47
|
from ..metrics.core import _metrics_step
|
|
48
|
+
from ..ontosim import prepare_network
|
|
49
|
+
from ..sense import register_sigma_callback
|
|
19
50
|
from ..trace import register_trace
|
|
20
|
-
from ..
|
|
21
|
-
from ..
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,
|
|
26
59
|
)
|
|
27
|
-
from ..presets import get_preset
|
|
28
|
-
from ..config import apply_config
|
|
29
|
-
from ..io import read_structured_file, safe_write, StructuredFileError
|
|
30
|
-
from ..glyph_history import ensure_history
|
|
31
|
-
from ..ontosim import preparar_red
|
|
32
|
-
from ..logging_utils import get_logger
|
|
33
|
-
from ..types import Glyph
|
|
34
|
-
from ..json_utils import json_dumps
|
|
35
|
-
from ..flatten import parse_program_tokens
|
|
36
|
-
|
|
37
60
|
from .arguments import _args_to_dict
|
|
61
|
+
from .utils import _parse_cli_variants
|
|
62
|
+
from ..validation import validate_canon
|
|
38
63
|
|
|
39
64
|
logger = get_logger(__name__)
|
|
40
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
|
+
)
|
|
41
76
|
|
|
42
77
|
# CLI summaries should remain concise by default while allowing callers to
|
|
43
78
|
# inspect the full glyphogram series when needed.
|
|
44
79
|
DEFAULT_SUMMARY_SERIES_LIMIT = 10
|
|
45
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
|
+
|
|
46
119
|
|
|
47
120
|
def _save_json(path: str, data: Any) -> None:
|
|
48
121
|
payload = json_dumps(data, ensure_ascii=False, indent=2, default=list)
|
|
@@ -53,11 +126,17 @@ def _attach_callbacks(G: "nx.Graph") -> None:
|
|
|
53
126
|
register_sigma_callback(G)
|
|
54
127
|
register_metrics_callbacks(G)
|
|
55
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", [])
|
|
56
133
|
_metrics_step(G, ctx=None)
|
|
57
134
|
|
|
58
135
|
|
|
59
136
|
def _persist_history(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
60
|
-
if getattr(args, "save_history", None) or getattr(
|
|
137
|
+
if getattr(args, "save_history", None) or getattr(
|
|
138
|
+
args, "export_history_base", None
|
|
139
|
+
):
|
|
61
140
|
history = ensure_history(G)
|
|
62
141
|
if getattr(args, "save_history", None):
|
|
63
142
|
_save_json(args.save_history, history)
|
|
@@ -65,7 +144,153 @@ def _persist_history(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
|
65
144
|
export_metrics(G, args.export_history_base, fmt=args.export_format)
|
|
66
145
|
|
|
67
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
|
+
|
|
68
291
|
def build_basic_graph(args: argparse.Namespace) -> "nx.Graph":
|
|
292
|
+
"""Construct the base graph topology described by CLI ``args``."""
|
|
293
|
+
|
|
69
294
|
n = args.nodes
|
|
70
295
|
topology = getattr(args, "topology", "ring").lower()
|
|
71
296
|
seed = getattr(args, "seed", None)
|
|
@@ -81,7 +306,7 @@ def build_basic_graph(args: argparse.Namespace) -> "nx.Graph":
|
|
|
81
306
|
fallback = 0.0
|
|
82
307
|
else:
|
|
83
308
|
fallback = 3.0 / n
|
|
84
|
-
prob =
|
|
309
|
+
prob = clamp01(fallback)
|
|
85
310
|
if not 0.0 <= prob <= 1.0:
|
|
86
311
|
raise ValueError(f"p must be between 0 and 1; received {prob}")
|
|
87
312
|
G = nx.gnp_random_graph(n, prob, seed=seed)
|
|
@@ -95,8 +320,14 @@ def build_basic_graph(args: argparse.Namespace) -> "nx.Graph":
|
|
|
95
320
|
|
|
96
321
|
|
|
97
322
|
def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
323
|
+
"""Apply CLI overrides from ``args`` to graph-level configuration."""
|
|
324
|
+
|
|
98
325
|
if args.config:
|
|
99
|
-
|
|
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
|
|
100
331
|
arg_map = {
|
|
101
332
|
"dt": ("DT", float),
|
|
102
333
|
"integrator": ("INTEGRATOR_METHOD", str),
|
|
@@ -108,8 +339,18 @@ def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
|
108
339
|
if val is not None:
|
|
109
340
|
G.graph[key] = conv(val)
|
|
110
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
|
+
|
|
111
352
|
gcanon = {
|
|
112
|
-
**
|
|
353
|
+
**base_gcanon,
|
|
113
354
|
**_args_to_dict(args, prefix="grammar_"),
|
|
114
355
|
}
|
|
115
356
|
if getattr(args, "grammar_canon", None) is not None:
|
|
@@ -122,9 +363,7 @@ def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
|
122
363
|
"basic": default_glyph_selector,
|
|
123
364
|
"param": parametric_glyph_selector,
|
|
124
365
|
}
|
|
125
|
-
G.graph["glyph_selector"] = sel_map.get(
|
|
126
|
-
selector, default_glyph_selector
|
|
127
|
-
)
|
|
366
|
+
G.graph["glyph_selector"] = sel_map.get(selector, default_glyph_selector)
|
|
128
367
|
|
|
129
368
|
if hasattr(args, "gamma_type"):
|
|
130
369
|
G.graph["GAMMA"] = {
|
|
@@ -133,8 +372,41 @@ def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
|
133
372
|
"R0": args.gamma_R0,
|
|
134
373
|
}
|
|
135
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
|
+
|
|
136
406
|
|
|
137
407
|
def register_callbacks_and_observer(G: "nx.Graph") -> None:
|
|
408
|
+
"""Attach callbacks and validators required for CLI runs."""
|
|
409
|
+
|
|
138
410
|
_attach_callbacks(G)
|
|
139
411
|
validate_canon(G)
|
|
140
412
|
|
|
@@ -144,12 +416,13 @@ def _build_graph_from_args(args: argparse.Namespace) -> "nx.Graph":
|
|
|
144
416
|
apply_cli_config(G, args)
|
|
145
417
|
if getattr(args, "observer", False):
|
|
146
418
|
G.graph["ATTACH_STD_OBSERVER"] = True
|
|
147
|
-
|
|
419
|
+
prepare_network(G)
|
|
148
420
|
register_callbacks_and_observer(G)
|
|
421
|
+
_configure_math_engine(G, args)
|
|
149
422
|
return G
|
|
150
423
|
|
|
151
424
|
|
|
152
|
-
def _load_sequence(path: Path) ->
|
|
425
|
+
def _load_sequence(path: Path) -> ProgramTokens:
|
|
153
426
|
try:
|
|
154
427
|
data = read_structured_file(path)
|
|
155
428
|
except (StructuredFileError, OSError) as exc:
|
|
@@ -159,19 +432,29 @@ def _load_sequence(path: Path) -> list[Any]:
|
|
|
159
432
|
message = str(StructuredFileError(path, exc))
|
|
160
433
|
logger.error("%s", message)
|
|
161
434
|
raise SystemExit(1) from exc
|
|
435
|
+
if isinstance(data, Mapping) and "sequence" in data:
|
|
436
|
+
data = data["sequence"]
|
|
162
437
|
return parse_program_tokens(data)
|
|
163
438
|
|
|
164
439
|
|
|
165
440
|
def resolve_program(
|
|
166
|
-
args: argparse.Namespace, default: Optional[
|
|
167
|
-
) -> Optional[
|
|
441
|
+
args: argparse.Namespace, default: Optional[ProgramTokens] = None
|
|
442
|
+
) -> Optional[ProgramTokens]:
|
|
443
|
+
"""Resolve preset/sequence inputs into program tokens."""
|
|
444
|
+
|
|
168
445
|
if getattr(args, "preset", None):
|
|
169
446
|
try:
|
|
170
447
|
return get_preset(args.preset)
|
|
171
448
|
except KeyError as exc:
|
|
449
|
+
details = exc.args[0] if exc.args else "Preset lookup failed."
|
|
172
450
|
logger.error(
|
|
173
|
-
|
|
451
|
+
(
|
|
452
|
+
"Unknown preset '%s'. Available presets: %s. %s "
|
|
453
|
+
"Use --sequence-file to execute custom sequences."
|
|
454
|
+
),
|
|
174
455
|
args.preset,
|
|
456
|
+
_PREFERRED_PRESETS_DISPLAY,
|
|
457
|
+
details,
|
|
175
458
|
)
|
|
176
459
|
raise SystemExit(1) from exc
|
|
177
460
|
if getattr(args, "sequence_file", None):
|
|
@@ -180,8 +463,12 @@ def resolve_program(
|
|
|
180
463
|
|
|
181
464
|
|
|
182
465
|
def run_program(
|
|
183
|
-
G: Optional["nx.Graph"],
|
|
466
|
+
G: Optional["nx.Graph"],
|
|
467
|
+
program: Optional[ProgramTokens],
|
|
468
|
+
args: argparse.Namespace,
|
|
184
469
|
) -> "nx.Graph":
|
|
470
|
+
"""Execute ``program`` (or timed run) on ``G`` using CLI options."""
|
|
471
|
+
|
|
185
472
|
if G is None:
|
|
186
473
|
G = _build_graph_from_args(args)
|
|
187
474
|
|
|
@@ -197,6 +484,13 @@ def run_program(
|
|
|
197
484
|
if value is not None:
|
|
198
485
|
run_kwargs[attr] = value
|
|
199
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
|
+
|
|
200
494
|
run(G, steps=steps, **run_kwargs)
|
|
201
495
|
else:
|
|
202
496
|
play(G, program)
|
|
@@ -208,7 +502,7 @@ def run_program(
|
|
|
208
502
|
def _run_cli_program(
|
|
209
503
|
args: argparse.Namespace,
|
|
210
504
|
*,
|
|
211
|
-
default_program: Optional[
|
|
505
|
+
default_program: Optional[ProgramTokens] = None,
|
|
212
506
|
graph: Optional["nx.Graph"] = None,
|
|
213
507
|
) -> tuple[int, Optional["nx.Graph"]]:
|
|
214
508
|
try:
|
|
@@ -217,10 +511,128 @@ def _run_cli_program(
|
|
|
217
511
|
code = exc.code if isinstance(exc.code, int) else 1
|
|
218
512
|
return code or 1, None
|
|
219
513
|
|
|
220
|
-
|
|
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
|
|
221
519
|
return 0, result_graph
|
|
222
520
|
|
|
223
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
|
+
|
|
224
636
|
def _log_run_summaries(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
225
637
|
cfg_coh = G.graph.get("COHERENCE", METRIC_DEFAULTS["COHERENCE"])
|
|
226
638
|
cfg_diag = G.graph.get("DIAGNOSIS", METRIC_DEFAULTS["DIAGNOSIS"])
|
|
@@ -229,26 +641,30 @@ def _log_run_summaries(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
|
229
641
|
if cfg_coh.get("enabled", True):
|
|
230
642
|
Wstats = hist.get(cfg_coh.get("stats_history_key", "W_stats"), [])
|
|
231
643
|
if Wstats:
|
|
232
|
-
logger.info("[COHERENCE]
|
|
644
|
+
logger.info("[COHERENCE] last step: %s", Wstats[-1])
|
|
233
645
|
|
|
234
646
|
if cfg_diag.get("enabled", True):
|
|
235
647
|
last_diag = hist.get(cfg_diag.get("history_key", "nodal_diag"), [])
|
|
236
648
|
if last_diag:
|
|
237
649
|
sample = list(last_diag[-1].values())[:3]
|
|
238
|
-
logger.info("[DIAGNOSIS]
|
|
650
|
+
logger.info("[DIAGNOSIS] sample: %s", sample)
|
|
239
651
|
|
|
240
652
|
if args.summary:
|
|
241
653
|
summary_limit = getattr(args, "summary_limit", DEFAULT_SUMMARY_SERIES_LIMIT)
|
|
242
654
|
summary, has_latency_values = build_metrics_summary(
|
|
243
655
|
G, series_limit=summary_limit
|
|
244
656
|
)
|
|
245
|
-
logger.info("Tg
|
|
246
|
-
logger.info("Top
|
|
657
|
+
logger.info("Global Tg: %s", summary["Tg_global"])
|
|
658
|
+
logger.info("Top operators by Tg: %s", glyph_top(G, k=5))
|
|
247
659
|
if has_latency_values:
|
|
248
|
-
logger.info("
|
|
660
|
+
logger.info("Average latency: %s", summary["latency_mean"])
|
|
661
|
+
|
|
662
|
+
_log_math_engine_summary(G)
|
|
249
663
|
|
|
250
664
|
|
|
251
665
|
def cmd_run(args: argparse.Namespace) -> int:
|
|
666
|
+
"""Execute ``tnfr run`` returning the exit status."""
|
|
667
|
+
|
|
252
668
|
code, graph = _run_cli_program(args)
|
|
253
669
|
if code != 0:
|
|
254
670
|
return code
|
|
@@ -259,18 +675,18 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
259
675
|
|
|
260
676
|
|
|
261
677
|
def cmd_sequence(args: argparse.Namespace) -> int:
|
|
678
|
+
"""Execute ``tnfr sequence`` returning the exit status."""
|
|
679
|
+
|
|
262
680
|
if args.preset and args.sequence_file:
|
|
263
|
-
logger.error(
|
|
264
|
-
"No se puede usar --preset y --sequence-file al mismo tiempo"
|
|
265
|
-
)
|
|
681
|
+
logger.error("Cannot use --preset and --sequence-file at the same time")
|
|
266
682
|
return 1
|
|
267
|
-
code, _ = _run_cli_program(
|
|
268
|
-
args, default_program=get_preset(CANONICAL_PRESET_NAME)
|
|
269
|
-
)
|
|
683
|
+
code, _ = _run_cli_program(args, default_program=get_preset(CANONICAL_PRESET_NAME))
|
|
270
684
|
return code
|
|
271
685
|
|
|
272
686
|
|
|
273
687
|
def cmd_metrics(args: argparse.Namespace) -> int:
|
|
688
|
+
"""Execute ``tnfr metrics`` returning the exit status."""
|
|
689
|
+
|
|
274
690
|
if getattr(args, "steps", None) is None:
|
|
275
691
|
# Default a longer run for metrics stability
|
|
276
692
|
args.steps = 200
|
|
@@ -286,3 +702,213 @@ def cmd_metrics(args: argparse.Namespace) -> int:
|
|
|
286
702
|
else:
|
|
287
703
|
logger.info("%s", json_dumps(out))
|
|
288
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
|