tnfr 4.5.2__py3-none-any.whl → 8.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
"""Advanced sequence visualizer for TNFR operator sequences.
|
|
2
|
+
|
|
3
|
+
This module implements comprehensive visualization tools for structural operator sequences,
|
|
4
|
+
including flow diagrams, health dashboards, pattern analysis, and frequency timelines.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
import matplotlib.patches as mpatches
|
|
13
|
+
import numpy as np
|
|
14
|
+
from matplotlib.figure import Figure
|
|
15
|
+
from matplotlib.axes import Axes
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from ..operators.health_analyzer import SequenceHealthMetrics
|
|
19
|
+
from ..operators.grammar import StructuralPattern
|
|
20
|
+
|
|
21
|
+
from ..config.operator_names import (
|
|
22
|
+
COHERENCE,
|
|
23
|
+
CONTRACTION,
|
|
24
|
+
COUPLING,
|
|
25
|
+
DISSONANCE,
|
|
26
|
+
EMISSION,
|
|
27
|
+
EXPANSION,
|
|
28
|
+
MUTATION,
|
|
29
|
+
RECEPTION,
|
|
30
|
+
RECURSIVITY,
|
|
31
|
+
RESONANCE,
|
|
32
|
+
SELF_ORGANIZATION,
|
|
33
|
+
SILENCE,
|
|
34
|
+
TRANSITION,
|
|
35
|
+
canonical_operator_name,
|
|
36
|
+
operator_display_name,
|
|
37
|
+
)
|
|
38
|
+
from ..validation.compatibility import CompatibilityLevel, get_compatibility_level
|
|
39
|
+
|
|
40
|
+
__all__ = ["SequenceVisualizer"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Color mapping for compatibility levels
|
|
44
|
+
COMPATIBILITY_COLORS = {
|
|
45
|
+
CompatibilityLevel.EXCELLENT: "#2ecc71", # Green
|
|
46
|
+
CompatibilityLevel.GOOD: "#3498db", # Blue
|
|
47
|
+
CompatibilityLevel.CAUTION: "#f39c12", # Orange
|
|
48
|
+
CompatibilityLevel.AVOID: "#e74c3c", # Red
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Color mapping for frequency levels
|
|
52
|
+
FREQUENCY_COLORS = {
|
|
53
|
+
"high": "#e74c3c", # Red - high energy
|
|
54
|
+
"medium": "#3498db", # Blue - moderate
|
|
55
|
+
"zero": "#95a5a6", # Gray - paused
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# Operator category colors for pattern analysis
|
|
59
|
+
OPERATOR_CATEGORY_COLORS = {
|
|
60
|
+
"initiator": "#9b59b6", # Purple
|
|
61
|
+
"stabilizer": "#2ecc71", # Green
|
|
62
|
+
"transformer": "#e67e22", # Orange
|
|
63
|
+
"amplifier": "#e74c3c", # Red
|
|
64
|
+
"organizer": "#1abc9c", # Teal
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_operator_category(operator: str) -> str:
|
|
69
|
+
"""Determine the structural category of an operator."""
|
|
70
|
+
if operator == EMISSION:
|
|
71
|
+
return "initiator"
|
|
72
|
+
elif operator in {COHERENCE, SILENCE}:
|
|
73
|
+
return "stabilizer"
|
|
74
|
+
elif operator in {DISSONANCE, MUTATION, TRANSITION}:
|
|
75
|
+
return "transformer"
|
|
76
|
+
elif operator in {RESONANCE, COUPLING}:
|
|
77
|
+
return "amplifier"
|
|
78
|
+
elif operator in {SELF_ORGANIZATION, RECURSIVITY}:
|
|
79
|
+
return "organizer"
|
|
80
|
+
else:
|
|
81
|
+
return "stabilizer" # Default for other operators
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class SequenceVisualizer:
|
|
85
|
+
"""Advanced visualizer for TNFR operator sequences.
|
|
86
|
+
|
|
87
|
+
Provides multiple visualization types:
|
|
88
|
+
- Sequence flow diagrams with transition compatibility coloring
|
|
89
|
+
- Health metrics dashboards with radar charts
|
|
90
|
+
- Pattern analysis with component highlighting
|
|
91
|
+
- Frequency timelines showing structural evolution
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
>>> from tnfr.visualization import SequenceVisualizer
|
|
96
|
+
>>> from tnfr.operators.grammar import validate_sequence_with_health
|
|
97
|
+
>>>
|
|
98
|
+
>>> sequence = ["emission", "reception", "coherence", "silence"]
|
|
99
|
+
>>> result = validate_sequence_with_health(sequence)
|
|
100
|
+
>>>
|
|
101
|
+
>>> visualizer = SequenceVisualizer()
|
|
102
|
+
>>> fig, ax = visualizer.plot_sequence_flow(sequence, result.health_metrics)
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, figsize: Tuple[float, float] = (12, 8), dpi: int = 100):
|
|
106
|
+
"""Initialize the sequence visualizer.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
figsize : Tuple[float, float], optional
|
|
111
|
+
Default figure size for plots, by default (12, 8)
|
|
112
|
+
dpi : int, optional
|
|
113
|
+
Default DPI for plots, by default 100
|
|
114
|
+
"""
|
|
115
|
+
self.figsize = figsize
|
|
116
|
+
self.dpi = dpi
|
|
117
|
+
|
|
118
|
+
def plot_sequence_flow(
|
|
119
|
+
self,
|
|
120
|
+
sequence: List[str],
|
|
121
|
+
health_metrics: Optional[SequenceHealthMetrics] = None,
|
|
122
|
+
save_path: Optional[str] = None,
|
|
123
|
+
) -> Tuple[Figure, Axes]:
|
|
124
|
+
"""Plot sequence flow diagram with compatibility-colored transitions.
|
|
125
|
+
|
|
126
|
+
Creates a flow diagram showing operators as nodes with arrows representing
|
|
127
|
+
transitions. Arrow colors indicate compatibility level (green=excellent,
|
|
128
|
+
blue=good, orange=caution, red=avoid).
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
sequence : List[str]
|
|
133
|
+
Sequence of operator names (canonical form)
|
|
134
|
+
health_metrics : SequenceHealthMetrics, optional
|
|
135
|
+
Health metrics to display alongside the flow
|
|
136
|
+
save_path : str, optional
|
|
137
|
+
Path to save the figure
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
Tuple[Figure, Axes]
|
|
142
|
+
The matplotlib figure and axes objects
|
|
143
|
+
|
|
144
|
+
Examples
|
|
145
|
+
--------
|
|
146
|
+
>>> visualizer = SequenceVisualizer()
|
|
147
|
+
>>> sequence = ["emission", "coherence", "resonance", "silence"]
|
|
148
|
+
>>> fig, ax = visualizer.plot_sequence_flow(sequence)
|
|
149
|
+
>>> fig.savefig("flow.png")
|
|
150
|
+
"""
|
|
151
|
+
fig, ax = plt.subplots(figsize=self.figsize, dpi=self.dpi)
|
|
152
|
+
|
|
153
|
+
if not sequence:
|
|
154
|
+
ax.text(0.5, 0.5, "Empty sequence", ha="center", va="center", fontsize=14)
|
|
155
|
+
ax.set_xlim(0, 1)
|
|
156
|
+
ax.set_ylim(0, 1)
|
|
157
|
+
ax.axis("off")
|
|
158
|
+
return fig, ax
|
|
159
|
+
|
|
160
|
+
# Normalize operator names
|
|
161
|
+
normalized = [canonical_operator_name(op) or op for op in sequence]
|
|
162
|
+
|
|
163
|
+
# Calculate positions for operators
|
|
164
|
+
n_ops = len(normalized)
|
|
165
|
+
positions = {}
|
|
166
|
+
|
|
167
|
+
if n_ops == 1:
|
|
168
|
+
positions[0] = (0.5, 0.5)
|
|
169
|
+
else:
|
|
170
|
+
# Arrange in a flowing pattern
|
|
171
|
+
for i, op in enumerate(normalized):
|
|
172
|
+
x = 0.15 + (i / (n_ops - 1)) * 0.7
|
|
173
|
+
# Add slight vertical variation for visual interest
|
|
174
|
+
y = 0.5 + 0.1 * np.sin(i * np.pi / 3)
|
|
175
|
+
positions[i] = (x, y)
|
|
176
|
+
|
|
177
|
+
# Draw transitions with compatibility coloring
|
|
178
|
+
for i in range(len(normalized) - 1):
|
|
179
|
+
curr_op = normalized[i]
|
|
180
|
+
next_op = normalized[i + 1]
|
|
181
|
+
|
|
182
|
+
# Get compatibility level
|
|
183
|
+
compat = get_compatibility_level(curr_op, next_op)
|
|
184
|
+
color = COMPATIBILITY_COLORS.get(compat, "#95a5a6")
|
|
185
|
+
|
|
186
|
+
# Draw arrow
|
|
187
|
+
start = positions[i]
|
|
188
|
+
end = positions[i + 1]
|
|
189
|
+
|
|
190
|
+
ax.annotate(
|
|
191
|
+
"",
|
|
192
|
+
xy=end,
|
|
193
|
+
xytext=start,
|
|
194
|
+
arrowprops=dict(
|
|
195
|
+
arrowstyle="->",
|
|
196
|
+
color=color,
|
|
197
|
+
lw=2.5,
|
|
198
|
+
connectionstyle="arc3,rad=0.1",
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Draw operator nodes
|
|
203
|
+
for i, op in enumerate(normalized):
|
|
204
|
+
pos = positions[i]
|
|
205
|
+
|
|
206
|
+
# Get operator category for coloring
|
|
207
|
+
category = _get_operator_category(op)
|
|
208
|
+
node_color = OPERATOR_CATEGORY_COLORS.get(category, "#95a5a6")
|
|
209
|
+
|
|
210
|
+
# Note: Frequency-based styling removed (R5 constraint eliminated)
|
|
211
|
+
# All operators now use standard border width
|
|
212
|
+
border_width = 2
|
|
213
|
+
|
|
214
|
+
# Draw node
|
|
215
|
+
circle = plt.Circle(
|
|
216
|
+
pos, 0.04, color=node_color, ec="black", lw=border_width, zorder=10
|
|
217
|
+
)
|
|
218
|
+
ax.add_patch(circle)
|
|
219
|
+
|
|
220
|
+
# Add operator label
|
|
221
|
+
display_name = operator_display_name(op) or op
|
|
222
|
+
ax.text(
|
|
223
|
+
pos[0],
|
|
224
|
+
pos[1] - 0.08,
|
|
225
|
+
display_name,
|
|
226
|
+
ha="center",
|
|
227
|
+
va="top",
|
|
228
|
+
fontsize=10,
|
|
229
|
+
weight="bold",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Add frequency indicator
|
|
233
|
+
freq_label = f"νf={freq}"
|
|
234
|
+
ax.text(
|
|
235
|
+
pos[0],
|
|
236
|
+
pos[1] + 0.06,
|
|
237
|
+
freq_label,
|
|
238
|
+
ha="center",
|
|
239
|
+
va="bottom",
|
|
240
|
+
fontsize=8,
|
|
241
|
+
style="italic",
|
|
242
|
+
color=FREQUENCY_COLORS.get(freq, "#95a5a6"),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Add title
|
|
246
|
+
title = "TNFR Sequence Flow Diagram"
|
|
247
|
+
if health_metrics:
|
|
248
|
+
title += f"\nOverall Health: {health_metrics.overall_health:.2f}"
|
|
249
|
+
ax.set_title(title, fontsize=14, weight="bold", pad=20)
|
|
250
|
+
|
|
251
|
+
# Add legend
|
|
252
|
+
legend_elements = [
|
|
253
|
+
mpatches.Patch(
|
|
254
|
+
color=COMPATIBILITY_COLORS[CompatibilityLevel.EXCELLENT],
|
|
255
|
+
label="Excellent transition",
|
|
256
|
+
),
|
|
257
|
+
mpatches.Patch(
|
|
258
|
+
color=COMPATIBILITY_COLORS[CompatibilityLevel.GOOD],
|
|
259
|
+
label="Good transition",
|
|
260
|
+
),
|
|
261
|
+
mpatches.Patch(
|
|
262
|
+
color=COMPATIBILITY_COLORS[CompatibilityLevel.CAUTION],
|
|
263
|
+
label="Caution transition",
|
|
264
|
+
),
|
|
265
|
+
mpatches.Patch(
|
|
266
|
+
color=COMPATIBILITY_COLORS[CompatibilityLevel.AVOID],
|
|
267
|
+
label="Avoid transition",
|
|
268
|
+
),
|
|
269
|
+
]
|
|
270
|
+
ax.legend(handles=legend_elements, loc="upper right", fontsize=9)
|
|
271
|
+
|
|
272
|
+
# Add health metrics sidebar if provided
|
|
273
|
+
if health_metrics:
|
|
274
|
+
metrics_text = (
|
|
275
|
+
f"Coherence: {health_metrics.coherence_index:.2f}\n"
|
|
276
|
+
f"Balance: {health_metrics.balance_score:.2f}\n"
|
|
277
|
+
f"Sustainability: {health_metrics.sustainability_index:.2f}\n"
|
|
278
|
+
f"Pattern: {health_metrics.dominant_pattern}"
|
|
279
|
+
)
|
|
280
|
+
ax.text(
|
|
281
|
+
0.02,
|
|
282
|
+
0.98,
|
|
283
|
+
metrics_text,
|
|
284
|
+
transform=ax.transAxes,
|
|
285
|
+
fontsize=9,
|
|
286
|
+
va="top",
|
|
287
|
+
ha="left",
|
|
288
|
+
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
ax.set_xlim(0, 1)
|
|
292
|
+
ax.set_ylim(0, 1)
|
|
293
|
+
ax.set_aspect("equal")
|
|
294
|
+
ax.axis("off")
|
|
295
|
+
|
|
296
|
+
plt.tight_layout()
|
|
297
|
+
|
|
298
|
+
if save_path:
|
|
299
|
+
fig.savefig(save_path, dpi=self.dpi, bbox_inches="tight")
|
|
300
|
+
|
|
301
|
+
return fig, ax
|
|
302
|
+
|
|
303
|
+
def plot_health_dashboard(
|
|
304
|
+
self,
|
|
305
|
+
health_metrics: SequenceHealthMetrics,
|
|
306
|
+
save_path: Optional[str] = None,
|
|
307
|
+
) -> Tuple[Figure, np.ndarray]:
|
|
308
|
+
"""Plot comprehensive health metrics dashboard with radar chart.
|
|
309
|
+
|
|
310
|
+
Creates a multi-panel dashboard showing:
|
|
311
|
+
- Radar chart with all health metrics
|
|
312
|
+
- Bar chart comparing metrics to benchmarks
|
|
313
|
+
- Overall health gauge
|
|
314
|
+
|
|
315
|
+
Parameters
|
|
316
|
+
----------
|
|
317
|
+
health_metrics : SequenceHealthMetrics
|
|
318
|
+
Health metrics to visualize
|
|
319
|
+
save_path : str, optional
|
|
320
|
+
Path to save the figure
|
|
321
|
+
|
|
322
|
+
Returns
|
|
323
|
+
-------
|
|
324
|
+
Tuple[Figure, np.ndarray]
|
|
325
|
+
The matplotlib figure and array of axes objects
|
|
326
|
+
|
|
327
|
+
Examples
|
|
328
|
+
--------
|
|
329
|
+
>>> from tnfr.operators.grammar import validate_sequence_with_health
|
|
330
|
+
>>> result = validate_sequence_with_health(["emission", "coherence"])
|
|
331
|
+
>>> visualizer = SequenceVisualizer()
|
|
332
|
+
>>> fig, axes = visualizer.plot_health_dashboard(result.health_metrics)
|
|
333
|
+
"""
|
|
334
|
+
fig = plt.figure(figsize=(14, 10), dpi=self.dpi)
|
|
335
|
+
gs = fig.add_gridspec(2, 2, hspace=0.3, wspace=0.3)
|
|
336
|
+
|
|
337
|
+
# Create subplots
|
|
338
|
+
ax_radar = fig.add_subplot(gs[0, 0], projection="polar")
|
|
339
|
+
ax_bars = fig.add_subplot(gs[0, 1])
|
|
340
|
+
ax_gauge = fig.add_subplot(gs[1, :])
|
|
341
|
+
|
|
342
|
+
# --- Radar Chart ---
|
|
343
|
+
metrics_labels = [
|
|
344
|
+
"Coherence",
|
|
345
|
+
"Balance",
|
|
346
|
+
"Sustainability",
|
|
347
|
+
"Efficiency",
|
|
348
|
+
"Frequency",
|
|
349
|
+
"Completeness",
|
|
350
|
+
"Smoothness",
|
|
351
|
+
]
|
|
352
|
+
metrics_values = [
|
|
353
|
+
health_metrics.coherence_index,
|
|
354
|
+
health_metrics.balance_score,
|
|
355
|
+
health_metrics.sustainability_index,
|
|
356
|
+
health_metrics.complexity_efficiency,
|
|
357
|
+
health_metrics.frequency_harmony,
|
|
358
|
+
health_metrics.pattern_completeness,
|
|
359
|
+
health_metrics.transition_smoothness,
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
# Number of variables
|
|
363
|
+
num_vars = len(metrics_labels)
|
|
364
|
+
|
|
365
|
+
# Compute angle for each axis
|
|
366
|
+
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
|
|
367
|
+
metrics_values_plot = metrics_values + [metrics_values[0]]
|
|
368
|
+
angles += angles[:1]
|
|
369
|
+
|
|
370
|
+
# Plot radar chart
|
|
371
|
+
ax_radar.plot(angles, metrics_values_plot, "o-", linewidth=2, color="#3498db")
|
|
372
|
+
ax_radar.fill(angles, metrics_values_plot, alpha=0.25, color="#3498db")
|
|
373
|
+
ax_radar.set_xticks(angles[:-1])
|
|
374
|
+
ax_radar.set_xticklabels(metrics_labels, size=9)
|
|
375
|
+
ax_radar.set_ylim(0, 1)
|
|
376
|
+
ax_radar.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0])
|
|
377
|
+
ax_radar.set_title("Health Metrics Radar", size=12, weight="bold", pad=20)
|
|
378
|
+
ax_radar.grid(True)
|
|
379
|
+
|
|
380
|
+
# --- Bar Chart ---
|
|
381
|
+
# Define benchmark values for ideal sequences
|
|
382
|
+
# These represent canonical TNFR targets for well-formed sequences
|
|
383
|
+
BENCHMARK_COHERENCE = 0.7
|
|
384
|
+
BENCHMARK_BALANCE = 0.6
|
|
385
|
+
BENCHMARK_SUSTAINABILITY = 0.7
|
|
386
|
+
BENCHMARK_EFFICIENCY = 0.6
|
|
387
|
+
BENCHMARK_FREQUENCY = 0.8
|
|
388
|
+
BENCHMARK_COMPLETENESS = 0.7
|
|
389
|
+
BENCHMARK_SMOOTHNESS = 0.9
|
|
390
|
+
|
|
391
|
+
benchmarks = [
|
|
392
|
+
BENCHMARK_COHERENCE,
|
|
393
|
+
BENCHMARK_BALANCE,
|
|
394
|
+
BENCHMARK_SUSTAINABILITY,
|
|
395
|
+
BENCHMARK_EFFICIENCY,
|
|
396
|
+
BENCHMARK_FREQUENCY,
|
|
397
|
+
BENCHMARK_COMPLETENESS,
|
|
398
|
+
BENCHMARK_SMOOTHNESS,
|
|
399
|
+
]
|
|
400
|
+
x_pos = np.arange(num_vars)
|
|
401
|
+
width = 0.35
|
|
402
|
+
|
|
403
|
+
bars1 = ax_bars.bar(
|
|
404
|
+
x_pos - width / 2, metrics_values, width, label="Current", color="#3498db"
|
|
405
|
+
)
|
|
406
|
+
bars2 = ax_bars.bar(
|
|
407
|
+
x_pos + width / 2,
|
|
408
|
+
benchmarks,
|
|
409
|
+
width,
|
|
410
|
+
label="Benchmark",
|
|
411
|
+
color="#95a5a6",
|
|
412
|
+
alpha=0.6,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
ax_bars.set_ylabel("Score", fontsize=10)
|
|
416
|
+
ax_bars.set_title("Metrics vs Benchmarks", fontsize=12, weight="bold")
|
|
417
|
+
ax_bars.set_xticks(x_pos)
|
|
418
|
+
ax_bars.set_xticklabels(
|
|
419
|
+
[label[:4] for label in metrics_labels], rotation=45, ha="right", fontsize=8
|
|
420
|
+
)
|
|
421
|
+
ax_bars.legend(fontsize=9)
|
|
422
|
+
ax_bars.set_ylim(0, 1.1)
|
|
423
|
+
ax_bars.grid(axis="y", alpha=0.3)
|
|
424
|
+
|
|
425
|
+
# Add value labels on bars
|
|
426
|
+
for bars in [bars1, bars2]:
|
|
427
|
+
for bar in bars:
|
|
428
|
+
height = bar.get_height()
|
|
429
|
+
ax_bars.text(
|
|
430
|
+
bar.get_x() + bar.get_width() / 2.0,
|
|
431
|
+
height,
|
|
432
|
+
f"{height:.2f}",
|
|
433
|
+
ha="center",
|
|
434
|
+
va="bottom",
|
|
435
|
+
fontsize=7,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
# --- Overall Health Gauge ---
|
|
439
|
+
overall = health_metrics.overall_health
|
|
440
|
+
|
|
441
|
+
# Determine color based on health
|
|
442
|
+
if overall >= 0.8:
|
|
443
|
+
gauge_color = "#2ecc71" # Excellent
|
|
444
|
+
status = "EXCELLENT"
|
|
445
|
+
elif overall >= 0.6:
|
|
446
|
+
gauge_color = "#3498db" # Good
|
|
447
|
+
status = "GOOD"
|
|
448
|
+
elif overall >= 0.4:
|
|
449
|
+
gauge_color = "#f39c12" # Fair
|
|
450
|
+
status = "FAIR"
|
|
451
|
+
else:
|
|
452
|
+
gauge_color = "#e74c3c" # Poor
|
|
453
|
+
status = "NEEDS IMPROVEMENT"
|
|
454
|
+
|
|
455
|
+
# Draw gauge background
|
|
456
|
+
ax_gauge.barh(0, 1, height=0.3, color="#ecf0f1", left=0)
|
|
457
|
+
# Draw gauge fill
|
|
458
|
+
ax_gauge.barh(0, overall, height=0.3, color=gauge_color, left=0)
|
|
459
|
+
|
|
460
|
+
# Add markers
|
|
461
|
+
for i in range(0, 11):
|
|
462
|
+
val = i / 10
|
|
463
|
+
ax_gauge.axvline(
|
|
464
|
+
val, color="gray", linestyle="--", alpha=0.3, linewidth=0.5
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
ax_gauge.set_xlim(0, 1)
|
|
468
|
+
ax_gauge.set_ylim(-0.5, 0.5)
|
|
469
|
+
ax_gauge.set_yticks([])
|
|
470
|
+
ax_gauge.set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1.0])
|
|
471
|
+
ax_gauge.set_xticklabels(["0.0", "0.2", "0.4", "0.6", "0.8", "1.0"])
|
|
472
|
+
|
|
473
|
+
# Add overall health value and status
|
|
474
|
+
ax_gauge.text(
|
|
475
|
+
0.5,
|
|
476
|
+
0.7,
|
|
477
|
+
f"Overall Health: {overall:.3f}",
|
|
478
|
+
ha="center",
|
|
479
|
+
va="center",
|
|
480
|
+
fontsize=16,
|
|
481
|
+
weight="bold",
|
|
482
|
+
transform=ax_gauge.transAxes,
|
|
483
|
+
)
|
|
484
|
+
ax_gauge.text(
|
|
485
|
+
0.5,
|
|
486
|
+
0.3,
|
|
487
|
+
status,
|
|
488
|
+
ha="center",
|
|
489
|
+
va="center",
|
|
490
|
+
fontsize=14,
|
|
491
|
+
weight="bold",
|
|
492
|
+
color=gauge_color,
|
|
493
|
+
transform=ax_gauge.transAxes,
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
# Add metadata
|
|
497
|
+
metadata_text = (
|
|
498
|
+
f"Sequence Length: {health_metrics.sequence_length}\n"
|
|
499
|
+
f"Dominant Pattern: {health_metrics.dominant_pattern}\n"
|
|
500
|
+
f"Recommendations: {len(health_metrics.recommendations)}"
|
|
501
|
+
)
|
|
502
|
+
ax_gauge.text(
|
|
503
|
+
0.02,
|
|
504
|
+
-0.4,
|
|
505
|
+
metadata_text,
|
|
506
|
+
ha="left",
|
|
507
|
+
va="top",
|
|
508
|
+
fontsize=9,
|
|
509
|
+
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.5),
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
ax_gauge.set_title(
|
|
513
|
+
"Overall Structural Health", fontsize=14, weight="bold", pad=20
|
|
514
|
+
)
|
|
515
|
+
ax_gauge.spines["top"].set_visible(False)
|
|
516
|
+
ax_gauge.spines["right"].set_visible(False)
|
|
517
|
+
ax_gauge.spines["left"].set_visible(False)
|
|
518
|
+
|
|
519
|
+
fig.suptitle(
|
|
520
|
+
"TNFR Sequence Health Dashboard", fontsize=16, weight="bold", y=0.98
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
plt.tight_layout(rect=[0, 0, 1, 0.96])
|
|
524
|
+
|
|
525
|
+
if save_path:
|
|
526
|
+
fig.savefig(save_path, dpi=self.dpi, bbox_inches="tight")
|
|
527
|
+
|
|
528
|
+
return fig, np.array([ax_radar, ax_bars, ax_gauge])
|
|
529
|
+
|
|
530
|
+
def plot_pattern_analysis(
|
|
531
|
+
self,
|
|
532
|
+
sequence: List[str],
|
|
533
|
+
pattern: str,
|
|
534
|
+
save_path: Optional[str] = None,
|
|
535
|
+
) -> Tuple[Figure, Axes]:
|
|
536
|
+
"""Plot pattern analysis with component highlighting.
|
|
537
|
+
|
|
538
|
+
Visualizes the detected pattern within the sequence, highlighting
|
|
539
|
+
key components and their structural roles.
|
|
540
|
+
|
|
541
|
+
Parameters
|
|
542
|
+
----------
|
|
543
|
+
sequence : List[str]
|
|
544
|
+
Sequence of operator names
|
|
545
|
+
pattern : str
|
|
546
|
+
Detected pattern name (e.g., "activation", "therapeutic")
|
|
547
|
+
save_path : str, optional
|
|
548
|
+
Path to save the figure
|
|
549
|
+
|
|
550
|
+
Returns
|
|
551
|
+
-------
|
|
552
|
+
Tuple[Figure, Axes]
|
|
553
|
+
The matplotlib figure and axes objects
|
|
554
|
+
"""
|
|
555
|
+
fig, ax = plt.subplots(figsize=(14, 6), dpi=self.dpi)
|
|
556
|
+
|
|
557
|
+
if not sequence:
|
|
558
|
+
ax.text(0.5, 0.5, "Empty sequence", ha="center", va="center", fontsize=14)
|
|
559
|
+
ax.set_xlim(0, 1)
|
|
560
|
+
ax.set_ylim(0, 1)
|
|
561
|
+
ax.axis("off")
|
|
562
|
+
return fig, ax
|
|
563
|
+
|
|
564
|
+
normalized = [canonical_operator_name(op) or op for op in sequence]
|
|
565
|
+
n_ops = len(normalized)
|
|
566
|
+
|
|
567
|
+
# Create horizontal layout
|
|
568
|
+
x_positions = np.linspace(0.1, 0.9, n_ops)
|
|
569
|
+
y_base = 0.5
|
|
570
|
+
|
|
571
|
+
# Draw operators with category-based coloring
|
|
572
|
+
for i, op in enumerate(normalized):
|
|
573
|
+
category = _get_operator_category(op)
|
|
574
|
+
color = OPERATOR_CATEGORY_COLORS.get(category, "#95a5a6")
|
|
575
|
+
|
|
576
|
+
# Draw operator box
|
|
577
|
+
box = mpatches.FancyBboxPatch(
|
|
578
|
+
(x_positions[i] - 0.03, y_base - 0.08),
|
|
579
|
+
0.06,
|
|
580
|
+
0.16,
|
|
581
|
+
boxstyle="round,pad=0.01",
|
|
582
|
+
facecolor=color,
|
|
583
|
+
edgecolor="black",
|
|
584
|
+
linewidth=2,
|
|
585
|
+
alpha=0.7,
|
|
586
|
+
)
|
|
587
|
+
ax.add_patch(box)
|
|
588
|
+
|
|
589
|
+
# Add operator name
|
|
590
|
+
display_name = operator_display_name(op) or op
|
|
591
|
+
ax.text(
|
|
592
|
+
x_positions[i],
|
|
593
|
+
y_base,
|
|
594
|
+
display_name,
|
|
595
|
+
ha="center",
|
|
596
|
+
va="center",
|
|
597
|
+
fontsize=9,
|
|
598
|
+
weight="bold",
|
|
599
|
+
color="white",
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
# Add category label below
|
|
603
|
+
ax.text(
|
|
604
|
+
x_positions[i],
|
|
605
|
+
y_base - 0.15,
|
|
606
|
+
category,
|
|
607
|
+
ha="center",
|
|
608
|
+
va="top",
|
|
609
|
+
fontsize=7,
|
|
610
|
+
style="italic",
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
# Draw connecting arrows
|
|
614
|
+
for i in range(n_ops - 1):
|
|
615
|
+
ax.annotate(
|
|
616
|
+
"",
|
|
617
|
+
xy=(x_positions[i + 1] - 0.03, y_base),
|
|
618
|
+
xytext=(x_positions[i] + 0.03, y_base),
|
|
619
|
+
arrowprops=dict(arrowstyle="->", lw=2, color="#34495e"),
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
# Add pattern name and description
|
|
623
|
+
ax.text(
|
|
624
|
+
0.5,
|
|
625
|
+
0.85,
|
|
626
|
+
f"Detected Pattern: {pattern.upper()}",
|
|
627
|
+
ha="center",
|
|
628
|
+
va="center",
|
|
629
|
+
fontsize=14,
|
|
630
|
+
weight="bold",
|
|
631
|
+
transform=ax.transAxes,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Add legend for categories
|
|
635
|
+
legend_elements = [
|
|
636
|
+
mpatches.Patch(
|
|
637
|
+
color=OPERATOR_CATEGORY_COLORS["initiator"], label="Initiator"
|
|
638
|
+
),
|
|
639
|
+
mpatches.Patch(
|
|
640
|
+
color=OPERATOR_CATEGORY_COLORS["stabilizer"], label="Stabilizer"
|
|
641
|
+
),
|
|
642
|
+
mpatches.Patch(
|
|
643
|
+
color=OPERATOR_CATEGORY_COLORS["transformer"], label="Transformer"
|
|
644
|
+
),
|
|
645
|
+
mpatches.Patch(
|
|
646
|
+
color=OPERATOR_CATEGORY_COLORS["amplifier"], label="Amplifier"
|
|
647
|
+
),
|
|
648
|
+
mpatches.Patch(
|
|
649
|
+
color=OPERATOR_CATEGORY_COLORS["organizer"], label="Organizer"
|
|
650
|
+
),
|
|
651
|
+
]
|
|
652
|
+
ax.legend(handles=legend_elements, loc="lower right", fontsize=9, ncol=5)
|
|
653
|
+
|
|
654
|
+
ax.set_xlim(0, 1)
|
|
655
|
+
ax.set_ylim(0, 1)
|
|
656
|
+
ax.set_aspect("equal")
|
|
657
|
+
ax.axis("off")
|
|
658
|
+
ax.set_title(
|
|
659
|
+
"TNFR Pattern Component Analysis", fontsize=14, weight="bold", pad=20
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
plt.tight_layout()
|
|
663
|
+
|
|
664
|
+
if save_path:
|
|
665
|
+
fig.savefig(save_path, dpi=self.dpi, bbox_inches="tight")
|
|
666
|
+
|
|
667
|
+
return fig, ax
|
|
668
|
+
|
|
669
|
+
def plot_operator_sequence(
|
|
670
|
+
self,
|
|
671
|
+
sequence: List[str],
|
|
672
|
+
save_path: Optional[str] = None,
|
|
673
|
+
) -> Tuple[Figure, Axes]:
|
|
674
|
+
"""Plot simple timeline of operators through the sequence.
|
|
675
|
+
|
|
676
|
+
Shows operator progression through the sequence with category-based coloring.
|
|
677
|
+
Note: Frequency validation (R5) has been removed from TNFR grammar as it
|
|
678
|
+
was not a fundamental physical constraint.
|
|
679
|
+
|
|
680
|
+
Parameters
|
|
681
|
+
----------
|
|
682
|
+
sequence : List[str]
|
|
683
|
+
Sequence of operator names
|
|
684
|
+
save_path : str, optional
|
|
685
|
+
Path to save the figure
|
|
686
|
+
|
|
687
|
+
Returns
|
|
688
|
+
-------
|
|
689
|
+
Tuple[Figure, Axes]
|
|
690
|
+
The matplotlib figure and axes objects
|
|
691
|
+
"""
|
|
692
|
+
fig, ax = plt.subplots(figsize=(14, 6), dpi=self.dpi)
|
|
693
|
+
|
|
694
|
+
if not sequence:
|
|
695
|
+
ax.text(0.5, 0.5, "Empty sequence", ha="center", va="center", fontsize=14)
|
|
696
|
+
return fig, ax
|
|
697
|
+
|
|
698
|
+
normalized = [canonical_operator_name(op) or op for op in sequence]
|
|
699
|
+
|
|
700
|
+
# Map operators to categories for consistent visual grouping
|
|
701
|
+
categories = [_get_operator_category(op) for op in normalized]
|
|
702
|
+
category_values = {
|
|
703
|
+
"generator": 3,
|
|
704
|
+
"stabilizer": 2,
|
|
705
|
+
"transformer": 3,
|
|
706
|
+
"connector": 2,
|
|
707
|
+
"closure": 1,
|
|
708
|
+
}
|
|
709
|
+
y_values = [category_values.get(cat, 2) for cat in categories]
|
|
710
|
+
|
|
711
|
+
# Plot operator line
|
|
712
|
+
x_pos = np.arange(len(normalized))
|
|
713
|
+
ax.plot(
|
|
714
|
+
x_pos,
|
|
715
|
+
y_values,
|
|
716
|
+
marker="o",
|
|
717
|
+
markersize=12,
|
|
718
|
+
linewidth=2.5,
|
|
719
|
+
color="#3498db",
|
|
720
|
+
label="Operator flow",
|
|
721
|
+
zorder=2,
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
# Annotate operators with category colors
|
|
725
|
+
for i, (op, cat) in enumerate(zip(normalized, categories)):
|
|
726
|
+
display_name = operator_display_name(op) or op
|
|
727
|
+
y_offset = 0.2 if i % 2 == 0 else -0.2
|
|
728
|
+
|
|
729
|
+
cat_color = OPERATOR_CATEGORY_COLORS.get(cat, "#95a5a6")
|
|
730
|
+
ax.annotate(
|
|
731
|
+
display_name,
|
|
732
|
+
xy=(x_pos[i], y_values[i]),
|
|
733
|
+
xytext=(x_pos[i], y_values[i] + y_offset),
|
|
734
|
+
ha="center",
|
|
735
|
+
va="center",
|
|
736
|
+
fontsize=10,
|
|
737
|
+
weight="bold",
|
|
738
|
+
bbox=dict(
|
|
739
|
+
boxstyle="round,pad=0.4",
|
|
740
|
+
facecolor=cat_color,
|
|
741
|
+
alpha=0.8,
|
|
742
|
+
edgecolor="black",
|
|
743
|
+
linewidth=1.5,
|
|
744
|
+
),
|
|
745
|
+
zorder=3,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
# Styling
|
|
749
|
+
ax.set_yticks([1, 2, 3])
|
|
750
|
+
ax.set_yticklabels(["Closure", "Moderate", "Intensive"], fontsize=11)
|
|
751
|
+
ax.set_xticks(x_pos)
|
|
752
|
+
ax.set_xticklabels([f"Step {i+1}" for i in range(len(normalized))], fontsize=9)
|
|
753
|
+
ax.set_ylabel("Operator Intensity", fontsize=12, weight="bold")
|
|
754
|
+
ax.set_xlabel("Sequence Position", fontsize=12, weight="bold")
|
|
755
|
+
ax.set_title(
|
|
756
|
+
"TNFR Operator Sequence Timeline", fontsize=14, weight="bold", pad=20
|
|
757
|
+
)
|
|
758
|
+
ax.grid(axis="y", alpha=0.3, linestyle="--")
|
|
759
|
+
ax.set_ylim(0.5, 3.5)
|
|
760
|
+
|
|
761
|
+
# Add category legend
|
|
762
|
+
legend_elements = [
|
|
763
|
+
mpatches.Patch(
|
|
764
|
+
color=OPERATOR_CATEGORY_COLORS["generator"], label="Generator"
|
|
765
|
+
),
|
|
766
|
+
mpatches.Patch(
|
|
767
|
+
color=OPERATOR_CATEGORY_COLORS["stabilizer"], label="Stabilizer"
|
|
768
|
+
),
|
|
769
|
+
mpatches.Patch(
|
|
770
|
+
color=OPERATOR_CATEGORY_COLORS["transformer"], label="Transformer"
|
|
771
|
+
),
|
|
772
|
+
mpatches.Patch(
|
|
773
|
+
color=OPERATOR_CATEGORY_COLORS["connector"], label="Connector"
|
|
774
|
+
),
|
|
775
|
+
mpatches.Patch(color=OPERATOR_CATEGORY_COLORS["closure"], label="Closure"),
|
|
776
|
+
]
|
|
777
|
+
ax.legend(handles=legend_elements, loc="upper right", fontsize=9, ncol=2)
|
|
778
|
+
|
|
779
|
+
plt.tight_layout()
|
|
780
|
+
|
|
781
|
+
if save_path:
|
|
782
|
+
fig.savefig(save_path, dpi=self.dpi, bbox_inches="tight")
|
|
783
|
+
|
|
784
|
+
return fig, ax
|