tnfr 4.5.2__py3-none-any.whl → 8.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +334 -50
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +214 -37
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +149 -556
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +51 -16
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +344 -32
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +676 -50
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +18 -3
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/{constants_glyphs.py → config/constants.py} +26 -20
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/{constants/core.py → config/defaults_core.py} +59 -6
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +51 -133
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +3 -1
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +9 -15
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +213 -633
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +2699 -398
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +496 -102
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +10 -5
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +77 -55
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +29 -50
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +66 -53
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +144 -57
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +70 -30
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +22 -16
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +5 -241
- tnfr/io.pyi +13 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +47 -9
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +1510 -330
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +23 -16
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +251 -36
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +709 -110
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +60 -18
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +233 -43
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +63 -28
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1126 -43
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +215 -23
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +148 -24
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +646 -140
- tnfr/node.pyi +139 -0
- tnfr/observers.py +160 -45
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +23 -19
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1358 -106
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +107 -38
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1415 -91
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +75 -151
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +59 -22
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +92 -67
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +639 -263
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +2 -4
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +300 -126
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +743 -12
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/{callback_utils.py → utils/callbacks.py} +26 -39
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/{helpers → utils}/numeric.py +51 -24
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/entry_points.txt +1 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/licenses/LICENSE.md +1 -1
- tnfr/collections_utils.py +0 -300
- tnfr/config.py +0 -32
- tnfr/grammar.py +0 -344
- tnfr/graph_utils.py +0 -84
- tnfr/helpers/__init__.py +0 -71
- tnfr/import_utils.py +0 -228
- tnfr/json_utils.py +0 -162
- tnfr/logging_utils.py +0 -116
- tnfr/presets.py +0 -60
- tnfr/validators.py +0 -84
- tnfr/value_utils.py +0 -59
- tnfr-4.5.2.dist-info/METADATA +0 -379
- tnfr-4.5.2.dist-info/RECORD +0 -67
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.2.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/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
|
tnfr/viz/matplotlib.pyi
ADDED
|
@@ -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]: ...
|