tnfr 3.0.3__py3-none-any.whl → 8.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tnfr might be problematic. Click here for more details.

Files changed (360) hide show
  1. tnfr/__init__.py +375 -56
  2. tnfr/__init__.pyi +33 -0
  3. tnfr/_compat.py +10 -0
  4. tnfr/_generated_version.py +34 -0
  5. tnfr/_version.py +49 -0
  6. tnfr/_version.pyi +7 -0
  7. tnfr/alias.py +723 -0
  8. tnfr/alias.pyi +108 -0
  9. tnfr/backends/__init__.py +354 -0
  10. tnfr/backends/jax_backend.py +173 -0
  11. tnfr/backends/numpy_backend.py +238 -0
  12. tnfr/backends/optimized_numpy.py +420 -0
  13. tnfr/backends/torch_backend.py +408 -0
  14. tnfr/cache.py +171 -0
  15. tnfr/cache.pyi +13 -0
  16. tnfr/cli/__init__.py +110 -0
  17. tnfr/cli/__init__.pyi +26 -0
  18. tnfr/cli/arguments.py +489 -0
  19. tnfr/cli/arguments.pyi +29 -0
  20. tnfr/cli/execution.py +914 -0
  21. tnfr/cli/execution.pyi +70 -0
  22. tnfr/cli/interactive_validator.py +614 -0
  23. tnfr/cli/utils.py +51 -0
  24. tnfr/cli/utils.pyi +7 -0
  25. tnfr/cli/validate.py +236 -0
  26. tnfr/compat/__init__.py +85 -0
  27. tnfr/compat/dataclass.py +136 -0
  28. tnfr/compat/jsonschema_stub.py +61 -0
  29. tnfr/compat/matplotlib_stub.py +73 -0
  30. tnfr/compat/numpy_stub.py +155 -0
  31. tnfr/config/__init__.py +224 -0
  32. tnfr/config/__init__.pyi +10 -0
  33. tnfr/config/constants.py +104 -0
  34. tnfr/config/constants.pyi +12 -0
  35. tnfr/config/defaults.py +54 -0
  36. tnfr/config/defaults_core.py +212 -0
  37. tnfr/config/defaults_init.py +33 -0
  38. tnfr/config/defaults_metric.py +104 -0
  39. tnfr/config/feature_flags.py +81 -0
  40. tnfr/config/feature_flags.pyi +16 -0
  41. tnfr/config/glyph_constants.py +31 -0
  42. tnfr/config/init.py +77 -0
  43. tnfr/config/init.pyi +8 -0
  44. tnfr/config/operator_names.py +254 -0
  45. tnfr/config/operator_names.pyi +36 -0
  46. tnfr/config/physics_derivation.py +354 -0
  47. tnfr/config/presets.py +83 -0
  48. tnfr/config/presets.pyi +7 -0
  49. tnfr/config/security.py +927 -0
  50. tnfr/config/thresholds.py +114 -0
  51. tnfr/config/tnfr_config.py +498 -0
  52. tnfr/constants/__init__.py +92 -0
  53. tnfr/constants/__init__.pyi +92 -0
  54. tnfr/constants/aliases.py +33 -0
  55. tnfr/constants/aliases.pyi +27 -0
  56. tnfr/constants/init.py +33 -0
  57. tnfr/constants/init.pyi +12 -0
  58. tnfr/constants/metric.py +104 -0
  59. tnfr/constants/metric.pyi +19 -0
  60. tnfr/core/__init__.py +33 -0
  61. tnfr/core/container.py +226 -0
  62. tnfr/core/default_implementations.py +329 -0
  63. tnfr/core/interfaces.py +279 -0
  64. tnfr/dynamics/__init__.py +238 -0
  65. tnfr/dynamics/__init__.pyi +83 -0
  66. tnfr/dynamics/adaptation.py +267 -0
  67. tnfr/dynamics/adaptation.pyi +7 -0
  68. tnfr/dynamics/adaptive_sequences.py +189 -0
  69. tnfr/dynamics/adaptive_sequences.pyi +14 -0
  70. tnfr/dynamics/aliases.py +23 -0
  71. tnfr/dynamics/aliases.pyi +19 -0
  72. tnfr/dynamics/bifurcation.py +232 -0
  73. tnfr/dynamics/canonical.py +229 -0
  74. tnfr/dynamics/canonical.pyi +48 -0
  75. tnfr/dynamics/coordination.py +385 -0
  76. tnfr/dynamics/coordination.pyi +25 -0
  77. tnfr/dynamics/dnfr.py +3034 -0
  78. tnfr/dynamics/dnfr.pyi +26 -0
  79. tnfr/dynamics/dynamic_limits.py +225 -0
  80. tnfr/dynamics/feedback.py +252 -0
  81. tnfr/dynamics/feedback.pyi +24 -0
  82. tnfr/dynamics/fused_dnfr.py +454 -0
  83. tnfr/dynamics/homeostasis.py +157 -0
  84. tnfr/dynamics/homeostasis.pyi +14 -0
  85. tnfr/dynamics/integrators.py +661 -0
  86. tnfr/dynamics/integrators.pyi +36 -0
  87. tnfr/dynamics/learning.py +310 -0
  88. tnfr/dynamics/learning.pyi +33 -0
  89. tnfr/dynamics/metabolism.py +254 -0
  90. tnfr/dynamics/nbody.py +796 -0
  91. tnfr/dynamics/nbody_tnfr.py +783 -0
  92. tnfr/dynamics/propagation.py +326 -0
  93. tnfr/dynamics/runtime.py +908 -0
  94. tnfr/dynamics/runtime.pyi +77 -0
  95. tnfr/dynamics/sampling.py +36 -0
  96. tnfr/dynamics/sampling.pyi +7 -0
  97. tnfr/dynamics/selectors.py +711 -0
  98. tnfr/dynamics/selectors.pyi +85 -0
  99. tnfr/dynamics/structural_clip.py +207 -0
  100. tnfr/errors/__init__.py +37 -0
  101. tnfr/errors/contextual.py +492 -0
  102. tnfr/execution.py +223 -0
  103. tnfr/execution.pyi +45 -0
  104. tnfr/extensions/__init__.py +205 -0
  105. tnfr/extensions/__init__.pyi +18 -0
  106. tnfr/extensions/base.py +173 -0
  107. tnfr/extensions/base.pyi +35 -0
  108. tnfr/extensions/business/__init__.py +71 -0
  109. tnfr/extensions/business/__init__.pyi +11 -0
  110. tnfr/extensions/business/cookbook.py +88 -0
  111. tnfr/extensions/business/cookbook.pyi +8 -0
  112. tnfr/extensions/business/health_analyzers.py +202 -0
  113. tnfr/extensions/business/health_analyzers.pyi +9 -0
  114. tnfr/extensions/business/patterns.py +183 -0
  115. tnfr/extensions/business/patterns.pyi +8 -0
  116. tnfr/extensions/medical/__init__.py +73 -0
  117. tnfr/extensions/medical/__init__.pyi +11 -0
  118. tnfr/extensions/medical/cookbook.py +88 -0
  119. tnfr/extensions/medical/cookbook.pyi +8 -0
  120. tnfr/extensions/medical/health_analyzers.py +181 -0
  121. tnfr/extensions/medical/health_analyzers.pyi +9 -0
  122. tnfr/extensions/medical/patterns.py +163 -0
  123. tnfr/extensions/medical/patterns.pyi +8 -0
  124. tnfr/flatten.py +262 -0
  125. tnfr/flatten.pyi +21 -0
  126. tnfr/gamma.py +354 -0
  127. tnfr/gamma.pyi +36 -0
  128. tnfr/glyph_history.py +377 -0
  129. tnfr/glyph_history.pyi +35 -0
  130. tnfr/glyph_runtime.py +19 -0
  131. tnfr/glyph_runtime.pyi +8 -0
  132. tnfr/immutable.py +218 -0
  133. tnfr/immutable.pyi +36 -0
  134. tnfr/initialization.py +203 -0
  135. tnfr/initialization.pyi +65 -0
  136. tnfr/io.py +10 -0
  137. tnfr/io.pyi +13 -0
  138. tnfr/locking.py +37 -0
  139. tnfr/locking.pyi +7 -0
  140. tnfr/mathematics/__init__.py +79 -0
  141. tnfr/mathematics/backend.py +453 -0
  142. tnfr/mathematics/backend.pyi +99 -0
  143. tnfr/mathematics/dynamics.py +408 -0
  144. tnfr/mathematics/dynamics.pyi +90 -0
  145. tnfr/mathematics/epi.py +391 -0
  146. tnfr/mathematics/epi.pyi +65 -0
  147. tnfr/mathematics/generators.py +242 -0
  148. tnfr/mathematics/generators.pyi +29 -0
  149. tnfr/mathematics/metrics.py +119 -0
  150. tnfr/mathematics/metrics.pyi +16 -0
  151. tnfr/mathematics/operators.py +239 -0
  152. tnfr/mathematics/operators.pyi +59 -0
  153. tnfr/mathematics/operators_factory.py +124 -0
  154. tnfr/mathematics/operators_factory.pyi +11 -0
  155. tnfr/mathematics/projection.py +87 -0
  156. tnfr/mathematics/projection.pyi +33 -0
  157. tnfr/mathematics/runtime.py +182 -0
  158. tnfr/mathematics/runtime.pyi +64 -0
  159. tnfr/mathematics/spaces.py +256 -0
  160. tnfr/mathematics/spaces.pyi +83 -0
  161. tnfr/mathematics/transforms.py +305 -0
  162. tnfr/mathematics/transforms.pyi +62 -0
  163. tnfr/metrics/__init__.py +79 -0
  164. tnfr/metrics/__init__.pyi +20 -0
  165. tnfr/metrics/buffer_cache.py +163 -0
  166. tnfr/metrics/buffer_cache.pyi +24 -0
  167. tnfr/metrics/cache_utils.py +214 -0
  168. tnfr/metrics/coherence.py +2009 -0
  169. tnfr/metrics/coherence.pyi +129 -0
  170. tnfr/metrics/common.py +158 -0
  171. tnfr/metrics/common.pyi +35 -0
  172. tnfr/metrics/core.py +316 -0
  173. tnfr/metrics/core.pyi +13 -0
  174. tnfr/metrics/diagnosis.py +833 -0
  175. tnfr/metrics/diagnosis.pyi +86 -0
  176. tnfr/metrics/emergence.py +245 -0
  177. tnfr/metrics/export.py +179 -0
  178. tnfr/metrics/export.pyi +7 -0
  179. tnfr/metrics/glyph_timing.py +379 -0
  180. tnfr/metrics/glyph_timing.pyi +81 -0
  181. tnfr/metrics/learning_metrics.py +280 -0
  182. tnfr/metrics/learning_metrics.pyi +21 -0
  183. tnfr/metrics/phase_coherence.py +351 -0
  184. tnfr/metrics/phase_compatibility.py +349 -0
  185. tnfr/metrics/reporting.py +183 -0
  186. tnfr/metrics/reporting.pyi +25 -0
  187. tnfr/metrics/sense_index.py +1203 -0
  188. tnfr/metrics/sense_index.pyi +9 -0
  189. tnfr/metrics/trig.py +373 -0
  190. tnfr/metrics/trig.pyi +13 -0
  191. tnfr/metrics/trig_cache.py +233 -0
  192. tnfr/metrics/trig_cache.pyi +10 -0
  193. tnfr/multiscale/__init__.py +32 -0
  194. tnfr/multiscale/hierarchical.py +517 -0
  195. tnfr/node.py +763 -0
  196. tnfr/node.pyi +139 -0
  197. tnfr/observers.py +255 -130
  198. tnfr/observers.pyi +31 -0
  199. tnfr/ontosim.py +144 -137
  200. tnfr/ontosim.pyi +28 -0
  201. tnfr/operators/__init__.py +1672 -0
  202. tnfr/operators/__init__.pyi +31 -0
  203. tnfr/operators/algebra.py +277 -0
  204. tnfr/operators/canonical_patterns.py +420 -0
  205. tnfr/operators/cascade.py +267 -0
  206. tnfr/operators/cycle_detection.py +358 -0
  207. tnfr/operators/definitions.py +4108 -0
  208. tnfr/operators/definitions.pyi +78 -0
  209. tnfr/operators/grammar.py +1164 -0
  210. tnfr/operators/grammar.pyi +140 -0
  211. tnfr/operators/hamiltonian.py +710 -0
  212. tnfr/operators/health_analyzer.py +809 -0
  213. tnfr/operators/jitter.py +272 -0
  214. tnfr/operators/jitter.pyi +11 -0
  215. tnfr/operators/lifecycle.py +314 -0
  216. tnfr/operators/metabolism.py +618 -0
  217. tnfr/operators/metrics.py +2138 -0
  218. tnfr/operators/network_analysis/__init__.py +27 -0
  219. tnfr/operators/network_analysis/source_detection.py +186 -0
  220. tnfr/operators/nodal_equation.py +395 -0
  221. tnfr/operators/pattern_detection.py +660 -0
  222. tnfr/operators/patterns.py +669 -0
  223. tnfr/operators/postconditions/__init__.py +38 -0
  224. tnfr/operators/postconditions/mutation.py +236 -0
  225. tnfr/operators/preconditions/__init__.py +1226 -0
  226. tnfr/operators/preconditions/coherence.py +305 -0
  227. tnfr/operators/preconditions/dissonance.py +236 -0
  228. tnfr/operators/preconditions/emission.py +128 -0
  229. tnfr/operators/preconditions/mutation.py +580 -0
  230. tnfr/operators/preconditions/reception.py +125 -0
  231. tnfr/operators/preconditions/resonance.py +364 -0
  232. tnfr/operators/registry.py +74 -0
  233. tnfr/operators/registry.pyi +9 -0
  234. tnfr/operators/remesh.py +1809 -0
  235. tnfr/operators/remesh.pyi +26 -0
  236. tnfr/operators/structural_units.py +268 -0
  237. tnfr/operators/unified_grammar.py +105 -0
  238. tnfr/parallel/__init__.py +54 -0
  239. tnfr/parallel/auto_scaler.py +234 -0
  240. tnfr/parallel/distributed.py +384 -0
  241. tnfr/parallel/engine.py +238 -0
  242. tnfr/parallel/gpu_engine.py +420 -0
  243. tnfr/parallel/monitoring.py +248 -0
  244. tnfr/parallel/partitioner.py +459 -0
  245. tnfr/py.typed +0 -0
  246. tnfr/recipes/__init__.py +22 -0
  247. tnfr/recipes/cookbook.py +743 -0
  248. tnfr/rng.py +178 -0
  249. tnfr/rng.pyi +26 -0
  250. tnfr/schemas/__init__.py +8 -0
  251. tnfr/schemas/grammar.json +94 -0
  252. tnfr/sdk/__init__.py +107 -0
  253. tnfr/sdk/__init__.pyi +19 -0
  254. tnfr/sdk/adaptive_system.py +173 -0
  255. tnfr/sdk/adaptive_system.pyi +21 -0
  256. tnfr/sdk/builders.py +370 -0
  257. tnfr/sdk/builders.pyi +51 -0
  258. tnfr/sdk/fluent.py +1121 -0
  259. tnfr/sdk/fluent.pyi +74 -0
  260. tnfr/sdk/templates.py +342 -0
  261. tnfr/sdk/templates.pyi +41 -0
  262. tnfr/sdk/utils.py +341 -0
  263. tnfr/secure_config.py +46 -0
  264. tnfr/security/__init__.py +70 -0
  265. tnfr/security/database.py +514 -0
  266. tnfr/security/subprocess.py +503 -0
  267. tnfr/security/validation.py +290 -0
  268. tnfr/selector.py +247 -0
  269. tnfr/selector.pyi +19 -0
  270. tnfr/sense.py +378 -0
  271. tnfr/sense.pyi +23 -0
  272. tnfr/services/__init__.py +17 -0
  273. tnfr/services/orchestrator.py +325 -0
  274. tnfr/sparse/__init__.py +39 -0
  275. tnfr/sparse/representations.py +492 -0
  276. tnfr/structural.py +705 -0
  277. tnfr/structural.pyi +83 -0
  278. tnfr/telemetry/__init__.py +35 -0
  279. tnfr/telemetry/cache_metrics.py +226 -0
  280. tnfr/telemetry/cache_metrics.pyi +64 -0
  281. tnfr/telemetry/nu_f.py +422 -0
  282. tnfr/telemetry/nu_f.pyi +108 -0
  283. tnfr/telemetry/verbosity.py +36 -0
  284. tnfr/telemetry/verbosity.pyi +15 -0
  285. tnfr/tokens.py +58 -0
  286. tnfr/tokens.pyi +36 -0
  287. tnfr/tools/__init__.py +20 -0
  288. tnfr/tools/domain_templates.py +478 -0
  289. tnfr/tools/sequence_generator.py +846 -0
  290. tnfr/topology/__init__.py +13 -0
  291. tnfr/topology/asymmetry.py +151 -0
  292. tnfr/trace.py +543 -0
  293. tnfr/trace.pyi +42 -0
  294. tnfr/tutorials/__init__.py +38 -0
  295. tnfr/tutorials/autonomous_evolution.py +285 -0
  296. tnfr/tutorials/interactive.py +1576 -0
  297. tnfr/tutorials/structural_metabolism.py +238 -0
  298. tnfr/types.py +775 -0
  299. tnfr/types.pyi +357 -0
  300. tnfr/units.py +68 -0
  301. tnfr/units.pyi +13 -0
  302. tnfr/utils/__init__.py +282 -0
  303. tnfr/utils/__init__.pyi +215 -0
  304. tnfr/utils/cache.py +4223 -0
  305. tnfr/utils/cache.pyi +470 -0
  306. tnfr/utils/callbacks.py +375 -0
  307. tnfr/utils/callbacks.pyi +49 -0
  308. tnfr/utils/chunks.py +108 -0
  309. tnfr/utils/chunks.pyi +22 -0
  310. tnfr/utils/data.py +428 -0
  311. tnfr/utils/data.pyi +74 -0
  312. tnfr/utils/graph.py +85 -0
  313. tnfr/utils/graph.pyi +10 -0
  314. tnfr/utils/init.py +821 -0
  315. tnfr/utils/init.pyi +80 -0
  316. tnfr/utils/io.py +559 -0
  317. tnfr/utils/io.pyi +66 -0
  318. tnfr/utils/numeric.py +114 -0
  319. tnfr/utils/numeric.pyi +21 -0
  320. tnfr/validation/__init__.py +257 -0
  321. tnfr/validation/__init__.pyi +85 -0
  322. tnfr/validation/compatibility.py +460 -0
  323. tnfr/validation/compatibility.pyi +6 -0
  324. tnfr/validation/config.py +73 -0
  325. tnfr/validation/graph.py +139 -0
  326. tnfr/validation/graph.pyi +18 -0
  327. tnfr/validation/input_validation.py +755 -0
  328. tnfr/validation/invariants.py +712 -0
  329. tnfr/validation/rules.py +253 -0
  330. tnfr/validation/rules.pyi +44 -0
  331. tnfr/validation/runtime.py +279 -0
  332. tnfr/validation/runtime.pyi +28 -0
  333. tnfr/validation/sequence_validator.py +162 -0
  334. tnfr/validation/soft_filters.py +170 -0
  335. tnfr/validation/soft_filters.pyi +32 -0
  336. tnfr/validation/spectral.py +164 -0
  337. tnfr/validation/spectral.pyi +42 -0
  338. tnfr/validation/validator.py +1266 -0
  339. tnfr/validation/window.py +39 -0
  340. tnfr/validation/window.pyi +1 -0
  341. tnfr/visualization/__init__.py +98 -0
  342. tnfr/visualization/cascade_viz.py +256 -0
  343. tnfr/visualization/hierarchy.py +284 -0
  344. tnfr/visualization/sequence_plotter.py +784 -0
  345. tnfr/viz/__init__.py +60 -0
  346. tnfr/viz/matplotlib.py +278 -0
  347. tnfr/viz/matplotlib.pyi +35 -0
  348. tnfr-8.5.0.dist-info/METADATA +573 -0
  349. tnfr-8.5.0.dist-info/RECORD +353 -0
  350. tnfr-8.5.0.dist-info/entry_points.txt +3 -0
  351. tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
  352. tnfr/constants.py +0 -183
  353. tnfr/dynamics.py +0 -543
  354. tnfr/helpers.py +0 -198
  355. tnfr/main.py +0 -37
  356. tnfr/operators.py +0 -296
  357. tnfr-3.0.3.dist-info/METADATA +0 -35
  358. tnfr-3.0.3.dist-info/RECORD +0 -13
  359. {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
  360. {tnfr-3.0.3.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