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
tnfr/viz/__init__.py ADDED
@@ -0,0 +1,60 @@
1
+ """Visualization helpers for TNFR telemetry.
2
+
3
+ This module requires optional dependencies (numpy, matplotlib). Install with::
4
+
5
+ pip install tnfr[viz]
6
+
7
+ or::
8
+
9
+ pip install numpy matplotlib
10
+ """
11
+
12
+ _import_error: ImportError | None = None
13
+
14
+ try:
15
+ from .matplotlib import plot_coherence_matrix, plot_phase_sync, plot_spectrum_path
16
+
17
+ __all__ = [
18
+ "plot_coherence_matrix",
19
+ "plot_phase_sync",
20
+ "plot_spectrum_path",
21
+ ]
22
+ except ImportError as _import_err:
23
+ # matplotlib or numpy not available - provide informative stubs
24
+ _import_error = _import_err
25
+ from typing import Any as _Any
26
+
27
+ def _missing_viz_dependency(*args: _Any, **kwargs: _Any) -> None:
28
+ # Provide more specific error message based on what's missing
29
+ missing_deps = []
30
+ try:
31
+ import numpy # noqa: F401
32
+ except ImportError:
33
+ missing_deps.append("numpy")
34
+ try:
35
+ import matplotlib # noqa: F401
36
+ except ImportError:
37
+ missing_deps.append("matplotlib")
38
+
39
+ if missing_deps:
40
+ deps_str = " and ".join(missing_deps)
41
+ raise ImportError(
42
+ f"Visualization functions require {deps_str}. "
43
+ "Install with: pip install tnfr[viz]"
44
+ ) from _import_error
45
+ else:
46
+ # Some other import error
47
+ raise ImportError(
48
+ "Visualization functions are not available. "
49
+ "Install with: pip install tnfr[viz]"
50
+ ) from _import_error
51
+
52
+ plot_coherence_matrix = _missing_viz_dependency # type: ignore[assignment]
53
+ plot_phase_sync = _missing_viz_dependency # type: ignore[assignment]
54
+ plot_spectrum_path = _missing_viz_dependency # type: ignore[assignment]
55
+
56
+ __all__ = [
57
+ "plot_coherence_matrix",
58
+ "plot_phase_sync",
59
+ "plot_spectrum_path",
60
+ ]
tnfr/viz/matplotlib.py ADDED
@@ -0,0 +1,278 @@
1
+ """Matplotlib plots for TNFR telemetry channels."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Iterable, Mapping, MutableMapping, Sequence
7
+
8
+ import numpy as np
9
+ from matplotlib import pyplot as plt
10
+ from matplotlib.axes import Axes
11
+ from matplotlib.figure import Figure
12
+
13
+ PathLike = str | Path
14
+
15
+
16
+ def _normalise_path(save_path: PathLike | None) -> Path | None:
17
+ """Normalize and validate a save path for visualization exports.
18
+
19
+ Parameters
20
+ ----------
21
+ save_path : str | Path | None
22
+ Path where the visualization should be saved, or None.
23
+
24
+ Returns
25
+ -------
26
+ Path | None
27
+ Validated and resolved path, or None if save_path is None.
28
+
29
+ Raises
30
+ ------
31
+ ValueError
32
+ If the path contains unsafe patterns or path traversal attempts.
33
+ """
34
+ if save_path is None:
35
+ return None
36
+
37
+ # Import security utilities
38
+ from ..security import validate_file_path, PathTraversalError
39
+
40
+ # Validate the path (allow absolute paths for save operations)
41
+ try:
42
+ validated = validate_file_path(
43
+ save_path,
44
+ allow_absolute=True,
45
+ allowed_extensions=None, # Allow various image formats
46
+ )
47
+ # Expand user home directory and resolve to absolute path
48
+ return validated.expanduser().resolve()
49
+ except (ValueError, PathTraversalError) as e:
50
+ raise ValueError(f"Invalid save path {save_path!r}: {e}") from e
51
+
52
+
53
+ def _prepare_metadata(
54
+ base: Mapping[str, str] | None = None, **entries: float | str
55
+ ) -> MutableMapping[str, str]:
56
+ metadata: MutableMapping[str, str] = {"engine": "TNFR"}
57
+ if base is not None:
58
+ metadata.update(base)
59
+ for key, value in entries.items():
60
+ metadata[key] = str(value)
61
+ return metadata
62
+
63
+
64
+ def plot_coherence_matrix(
65
+ coherence_matrix: np.ndarray,
66
+ *,
67
+ channels: Sequence[str] | None = None,
68
+ save_path: PathLike | None = None,
69
+ dpi: int = 300,
70
+ cmap: str = "viridis",
71
+ ) -> tuple[Figure, Axes]:
72
+ """Plot the coherence matrix :math:`C(t)` describing nodal coupling.
73
+
74
+ Parameters
75
+ ----------
76
+ coherence_matrix:
77
+ Square matrix reporting pairwise TNFR coherence (0-1). Each entry
78
+ encodes how two nodes sustain a mutual resonance while the total coherence
79
+ :math:`C(t)` evolves.
80
+ channels:
81
+ Optional channel names aligned with the matrix axes.
82
+ save_path:
83
+ Optional filesystem location. When provided the figure is exported with
84
+ explicit metadata so structural logs can capture how :math:`C(t)` was
85
+ rendered.
86
+ dpi:
87
+ Resolution of the exported artifact in dots per inch.
88
+ cmap:
89
+ Matplotlib colormap name used for the coherence heatmap.
90
+
91
+ Returns
92
+ -------
93
+ (Figure, Axes)
94
+ The Matplotlib figure and heatmap axis.
95
+ """
96
+
97
+ matrix = np.asarray(coherence_matrix, dtype=float)
98
+ if matrix.ndim != 2 or matrix.shape[0] != matrix.shape[1]:
99
+ raise ValueError("coherence_matrix must be a square 2D array")
100
+
101
+ fig, ax = plt.subplots(figsize=(6, 5))
102
+ image = ax.imshow(matrix, cmap=cmap, vmin=0.0, vmax=1.0)
103
+ ax.set_title("TNFR Coherence Matrix C(t)")
104
+ ax.set_xlabel("Emission nodes (νf order)")
105
+ ax.set_ylabel("Reception nodes (νf order)")
106
+ cbar = fig.colorbar(image, ax=ax, shrink=0.85)
107
+ cbar.set_label("Structural coherence C(t)")
108
+
109
+ size = matrix.shape[0]
110
+ ticks = np.arange(size)
111
+ ax.set_xticks(ticks)
112
+ ax.set_yticks(ticks)
113
+ if channels is not None and len(channels) == size:
114
+ ax.set_xticklabels(channels, rotation=45, ha="right")
115
+ ax.set_yticklabels(channels)
116
+
117
+ mean_coherence = float(matrix.mean())
118
+ metadata = _prepare_metadata(
119
+ {"tnfr_plot": "coherence_matrix"},
120
+ c_t_mean=mean_coherence,
121
+ phase_reference="synchrony-check",
122
+ )
123
+
124
+ resolved_path = _normalise_path(save_path)
125
+ if resolved_path is not None:
126
+ fig.savefig(
127
+ resolved_path,
128
+ dpi=dpi,
129
+ bbox_inches="tight",
130
+ metadata=metadata,
131
+ )
132
+
133
+ return fig, ax
134
+
135
+
136
+ def plot_phase_sync(
137
+ phase_paths: np.ndarray,
138
+ time_axis: np.ndarray,
139
+ *,
140
+ structural_frequency: float,
141
+ node_labels: Sequence[str] | None = None,
142
+ save_path: PathLike | None = None,
143
+ dpi: int = 300,
144
+ ) -> tuple[Figure, Axes]:
145
+ """Plot phase synchrony φ(t) trajectories for TNFR nodes.
146
+
147
+ Parameters
148
+ ----------
149
+ phase_paths:
150
+ Array with shape ``(nodes, samples)`` describing the phase of each node
151
+ in radians. Synchronised paths map how the coupling operator preserves
152
+ phase locking.
153
+ time_axis:
154
+ Monotonic timestamps aligned with the samples describing the evolution of
155
+ :math:`C(t)`.
156
+ structural_frequency:
157
+ Global structural frequency :math:`ν_f` (Hz_str) used as the reference
158
+ rate for the displayed phases.
159
+ node_labels:
160
+ Optional labels describing the emitting nodes. When omitted generic
161
+ indices are used.
162
+ save_path:
163
+ Optional filesystem location to export the figure with TNFR metadata.
164
+ dpi:
165
+ Resolution used for exported figures.
166
+
167
+ Returns
168
+ -------
169
+ (Figure, Axes)
170
+ The Matplotlib figure and axis holding the phase trajectories.
171
+ """
172
+
173
+ phases = np.asarray(phase_paths, dtype=float)
174
+ times = np.asarray(time_axis, dtype=float)
175
+ if phases.ndim != 2:
176
+ raise ValueError("phase_paths must be a 2D array")
177
+ if times.ndim != 1:
178
+ raise ValueError("time_axis must be a 1D array")
179
+ if phases.shape[1] != times.shape[0]:
180
+ raise ValueError("phase_paths samples must align with time_axis")
181
+
182
+ fig, ax = plt.subplots(figsize=(7, 4))
183
+ labels: Iterable[str]
184
+ if node_labels is not None and len(node_labels) == phases.shape[0]:
185
+ labels = node_labels
186
+ else:
187
+ labels = (f"node {idx}" for idx in range(phases.shape[0]))
188
+
189
+ for path, label in zip(phases, labels):
190
+ ax.plot(times, path, label=label)
191
+
192
+ ax.set_title("TNFR Phase Synchrony φ(t)")
193
+ ax.set_xlabel("Time (structural cycles)")
194
+ ax.set_ylabel("Phase (rad)")
195
+ ax.legend(loc="best")
196
+
197
+ metadata = _prepare_metadata(
198
+ {"tnfr_plot": "phase_sync"},
199
+ nu_f_hz_str=structural_frequency,
200
+ phase_span=float(np.ptp(phases)),
201
+ )
202
+
203
+ resolved_path = _normalise_path(save_path)
204
+ if resolved_path is not None:
205
+ fig.savefig(
206
+ resolved_path,
207
+ dpi=dpi,
208
+ bbox_inches="tight",
209
+ metadata=metadata,
210
+ )
211
+
212
+ return fig, ax
213
+
214
+
215
+ def plot_spectrum_path(
216
+ frequencies: np.ndarray,
217
+ spectrum: np.ndarray,
218
+ *,
219
+ label: str = "C(t) spectral density",
220
+ save_path: PathLike | None = None,
221
+ dpi: int = 300,
222
+ ) -> tuple[Figure, Axes]:
223
+ """Plot the spectral path of coherence intensity over structural frequency.
224
+
225
+ Parameters
226
+ ----------
227
+ frequencies:
228
+ Frequency samples (Hz_str) that describe the spectrum of ΔNFR driven
229
+ reorganisations.
230
+ spectrum:
231
+ Intensity values tracking how coherence redistributes along the
232
+ structural frequency axis.
233
+ label:
234
+ Legend label identifying the traced path of :math:`C(t)`.
235
+ save_path:
236
+ Optional filesystem location to persist the figure with TNFR metadata.
237
+ dpi:
238
+ Resolution used for exported figures.
239
+
240
+ Returns
241
+ -------
242
+ (Figure, Axes)
243
+ The Matplotlib figure and axis holding the spectrum path.
244
+ """
245
+
246
+ freq = np.asarray(frequencies, dtype=float)
247
+ spec = np.asarray(spectrum, dtype=float)
248
+ if freq.ndim != 1:
249
+ raise ValueError("frequencies must be a 1D array")
250
+ if spec.ndim != 1:
251
+ raise ValueError("spectrum must be a 1D array")
252
+ if freq.shape[0] != spec.shape[0]:
253
+ raise ValueError("frequencies and spectrum must share the same length")
254
+
255
+ fig, ax = plt.subplots(figsize=(7, 4))
256
+ ax.plot(freq, spec, marker="o", label=label)
257
+ ax.fill_between(freq, spec, alpha=0.2)
258
+ ax.set_title("TNFR Structural Spectrum")
259
+ ax.set_xlabel("Structural frequency ν_f (Hz_str)")
260
+ ax.set_ylabel("Coherence intensity C(t)")
261
+ ax.legend(loc="best")
262
+
263
+ metadata = _prepare_metadata(
264
+ {"tnfr_plot": "spectrum_path"},
265
+ nu_f_min=float(freq.min()),
266
+ nu_f_max=float(freq.max()),
267
+ )
268
+
269
+ resolved_path = _normalise_path(save_path)
270
+ if resolved_path is not None:
271
+ fig.savefig(
272
+ resolved_path,
273
+ dpi=dpi,
274
+ bbox_inches="tight",
275
+ metadata=metadata,
276
+ )
277
+
278
+ return fig, ax
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from matplotlib.axes import Axes
5
+ from matplotlib.figure import Figure
6
+ from pathlib import Path
7
+ from typing import Sequence
8
+
9
+ PathLike = str | Path
10
+
11
+ def plot_coherence_matrix(
12
+ coherence_matrix: np.ndarray,
13
+ *,
14
+ channels: Sequence[str] | None = None,
15
+ save_path: PathLike | None = None,
16
+ dpi: int = 300,
17
+ cmap: str = "viridis",
18
+ ) -> tuple[Figure, Axes]: ...
19
+ def plot_phase_sync(
20
+ phase_paths: np.ndarray,
21
+ time_axis: np.ndarray,
22
+ *,
23
+ structural_frequency: float,
24
+ node_labels: Sequence[str] | None = None,
25
+ save_path: PathLike | None = None,
26
+ dpi: int = 300,
27
+ ) -> tuple[Figure, Axes]: ...
28
+ def plot_spectrum_path(
29
+ frequencies: np.ndarray,
30
+ spectrum: np.ndarray,
31
+ *,
32
+ label: str = "C(t) spectral density",
33
+ save_path: PathLike | None = None,
34
+ dpi: int = 300,
35
+ ) -> tuple[Figure, Axes]: ...