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.
- tnfr/__init__.py +375 -56
- 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 +723 -0
- 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 +171 -0
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +110 -0
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +489 -0
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +914 -0
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +51 -0
- 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/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/config/defaults_core.py +212 -0
- 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 +92 -0
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +33 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +104 -0
- 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 +238 -0
- 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 +3034 -0
- 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 +661 -0
- 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 +36 -0
- 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 +223 -0
- 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 +262 -0
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +354 -0
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +377 -0
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +218 -0
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +203 -0
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +10 -0
- tnfr/io.pyi +13 -0
- tnfr/locking.py +37 -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 +79 -0
- 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 +2009 -0
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +158 -0
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +316 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +833 -0
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +179 -0
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +379 -0
- 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 +183 -0
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1203 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +373 -0
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +233 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +763 -0
- tnfr/node.pyi +139 -0
- tnfr/observers.py +255 -130
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +144 -137
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1672 -0
- 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 +272 -0
- 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 +1809 -0
- 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 +178 -0
- 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 +247 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +378 -0
- 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 +705 -0
- 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 +58 -0
- 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 +543 -0
- 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 +775 -0
- 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/utils/callbacks.py +375 -0
- 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/utils/numeric.py +114 -0
- 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-8.5.0.dist-info/entry_points.txt +3 -0
- tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
- tnfr/constants.py +0 -183
- tnfr/dynamics.py +0 -543
- tnfr/helpers.py +0 -198
- tnfr/main.py +0 -37
- tnfr/operators.py +0 -296
- tnfr-3.0.3.dist-info/METADATA +0 -35
- tnfr-3.0.3.dist-info/RECORD +0 -13
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""Input validation utilities for TNFR structural data.
|
|
2
|
+
|
|
3
|
+
This module provides validation functions for TNFR-specific data types
|
|
4
|
+
to ensure structural coherence when persisting or querying data.
|
|
5
|
+
|
|
6
|
+
TNFR Structural Invariants
|
|
7
|
+
---------------------------
|
|
8
|
+
These validators enforce TNFR canonical invariants:
|
|
9
|
+
1. Structural frequency (νf) in Hz_str units
|
|
10
|
+
2. Phase (φ) in valid range [0, 2π]
|
|
11
|
+
3. Coherence C(t) as non-negative value
|
|
12
|
+
4. Sense index Si validation
|
|
13
|
+
|
|
14
|
+
Example
|
|
15
|
+
-------
|
|
16
|
+
>>> validate_structural_frequency(0.5) # Valid
|
|
17
|
+
0.5
|
|
18
|
+
>>> validate_phase_value(3.14) # Valid
|
|
19
|
+
3.14
|
|
20
|
+
>>> validate_structural_frequency(-1.0) # doctest: +SKIP
|
|
21
|
+
Traceback (most recent call last):
|
|
22
|
+
...
|
|
23
|
+
ValueError: Structural frequency must be non-negative
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import math
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validate_structural_frequency(nu_f: float) -> float:
|
|
33
|
+
"""Validate structural frequency (νf) value.
|
|
34
|
+
|
|
35
|
+
Structural frequency must be non-negative and expressed in Hz_str
|
|
36
|
+
(structural hertz), representing the nodal reorganization rate.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
nu_f : float
|
|
41
|
+
Structural frequency value to validate
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
float
|
|
46
|
+
The validated frequency value
|
|
47
|
+
|
|
48
|
+
Raises
|
|
49
|
+
------
|
|
50
|
+
ValueError
|
|
51
|
+
If the frequency is negative, NaN, or infinite
|
|
52
|
+
|
|
53
|
+
Example
|
|
54
|
+
-------
|
|
55
|
+
>>> validate_structural_frequency(0.5)
|
|
56
|
+
0.5
|
|
57
|
+
>>> validate_structural_frequency(0.0) # Silence operator
|
|
58
|
+
0.0
|
|
59
|
+
>>> validate_structural_frequency(-0.1) # doctest: +SKIP
|
|
60
|
+
Traceback (most recent call last):
|
|
61
|
+
...
|
|
62
|
+
ValueError: Structural frequency must be non-negative, got -0.1
|
|
63
|
+
"""
|
|
64
|
+
if not isinstance(nu_f, (int, float)):
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Structural frequency must be numeric, got {type(nu_f).__name__}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if math.isnan(nu_f):
|
|
70
|
+
raise ValueError("Structural frequency cannot be NaN")
|
|
71
|
+
|
|
72
|
+
if math.isinf(nu_f):
|
|
73
|
+
raise ValueError("Structural frequency cannot be infinite")
|
|
74
|
+
|
|
75
|
+
if nu_f < 0:
|
|
76
|
+
raise ValueError(f"Structural frequency must be non-negative, got {nu_f}")
|
|
77
|
+
|
|
78
|
+
return float(nu_f)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def validate_phase_value(phase: float, *, allow_wrap: bool = True) -> float:
|
|
82
|
+
"""Validate phase (φ) value.
|
|
83
|
+
|
|
84
|
+
Phase represents synchronization in the network and should be in the
|
|
85
|
+
range [0, 2π]. If allow_wrap is True, values outside this range are
|
|
86
|
+
automatically wrapped.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
phase : float
|
|
91
|
+
Phase value to validate
|
|
92
|
+
allow_wrap : bool, optional
|
|
93
|
+
If True, wrap phase to [0, 2π] range (default: True)
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
float
|
|
98
|
+
The validated (and possibly wrapped) phase value
|
|
99
|
+
|
|
100
|
+
Raises
|
|
101
|
+
------
|
|
102
|
+
ValueError
|
|
103
|
+
If phase is NaN, infinite, or outside valid range (when allow_wrap=False)
|
|
104
|
+
|
|
105
|
+
Example
|
|
106
|
+
-------
|
|
107
|
+
>>> validate_phase_value(1.57) # π/2
|
|
108
|
+
1.57
|
|
109
|
+
>>> result = validate_phase_value(7.0) # Wrapped to [0, 2π]
|
|
110
|
+
>>> 0.0 <= result <= 6.3
|
|
111
|
+
True
|
|
112
|
+
>>> validate_phase_value(7.0, allow_wrap=False) # doctest: +SKIP
|
|
113
|
+
Traceback (most recent call last):
|
|
114
|
+
...
|
|
115
|
+
ValueError: Phase must be in range [0, 2π], got 7.0
|
|
116
|
+
"""
|
|
117
|
+
if not isinstance(phase, (int, float)):
|
|
118
|
+
raise ValueError(f"Phase must be numeric, got {type(phase).__name__}")
|
|
119
|
+
|
|
120
|
+
if math.isnan(phase):
|
|
121
|
+
raise ValueError("Phase cannot be NaN")
|
|
122
|
+
|
|
123
|
+
if math.isinf(phase):
|
|
124
|
+
raise ValueError("Phase cannot be infinite")
|
|
125
|
+
|
|
126
|
+
two_pi = 2 * math.pi
|
|
127
|
+
|
|
128
|
+
if allow_wrap:
|
|
129
|
+
# Wrap phase to [0, 2π] range
|
|
130
|
+
phase = phase % two_pi
|
|
131
|
+
else:
|
|
132
|
+
if not 0 <= phase <= two_pi:
|
|
133
|
+
raise ValueError(f"Phase must be in range [0, 2π], got {phase}")
|
|
134
|
+
|
|
135
|
+
return float(phase)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def validate_coherence_value(coherence: float) -> float:
|
|
139
|
+
"""Validate coherence C(t) value.
|
|
140
|
+
|
|
141
|
+
Coherence represents the total structural stability and must be
|
|
142
|
+
non-negative.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
coherence : float
|
|
147
|
+
Coherence value to validate
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
float
|
|
152
|
+
The validated coherence value
|
|
153
|
+
|
|
154
|
+
Raises
|
|
155
|
+
------
|
|
156
|
+
ValueError
|
|
157
|
+
If coherence is negative, NaN, or infinite
|
|
158
|
+
|
|
159
|
+
Example
|
|
160
|
+
-------
|
|
161
|
+
>>> validate_coherence_value(0.8)
|
|
162
|
+
0.8
|
|
163
|
+
>>> validate_coherence_value(0.0) # Minimum coherence
|
|
164
|
+
0.0
|
|
165
|
+
>>> validate_coherence_value(-0.1) # doctest: +SKIP
|
|
166
|
+
Traceback (most recent call last):
|
|
167
|
+
...
|
|
168
|
+
ValueError: Coherence must be non-negative, got -0.1
|
|
169
|
+
"""
|
|
170
|
+
if not isinstance(coherence, (int, float)):
|
|
171
|
+
raise ValueError(f"Coherence must be numeric, got {type(coherence).__name__}")
|
|
172
|
+
|
|
173
|
+
if math.isnan(coherence):
|
|
174
|
+
raise ValueError("Coherence cannot be NaN")
|
|
175
|
+
|
|
176
|
+
if math.isinf(coherence):
|
|
177
|
+
raise ValueError("Coherence cannot be infinite")
|
|
178
|
+
|
|
179
|
+
if coherence < 0:
|
|
180
|
+
raise ValueError(f"Coherence must be non-negative, got {coherence}")
|
|
181
|
+
|
|
182
|
+
return float(coherence)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def validate_sense_index(si: float) -> float:
|
|
186
|
+
"""Validate sense index (Si) value.
|
|
187
|
+
|
|
188
|
+
Sense index represents the capacity to generate stable reorganization.
|
|
189
|
+
Valid range is typically [0, 1] but can exceed 1 in high-coherence networks.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
si : float
|
|
194
|
+
Sense index value to validate
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
float
|
|
199
|
+
The validated sense index value
|
|
200
|
+
|
|
201
|
+
Raises
|
|
202
|
+
------
|
|
203
|
+
ValueError
|
|
204
|
+
If Si is negative, NaN, or infinite
|
|
205
|
+
|
|
206
|
+
Example
|
|
207
|
+
-------
|
|
208
|
+
>>> validate_sense_index(0.7)
|
|
209
|
+
0.7
|
|
210
|
+
>>> validate_sense_index(1.2) # High coherence
|
|
211
|
+
1.2
|
|
212
|
+
>>> validate_sense_index(-0.1) # doctest: +SKIP
|
|
213
|
+
Traceback (most recent call last):
|
|
214
|
+
...
|
|
215
|
+
ValueError: Sense index must be non-negative, got -0.1
|
|
216
|
+
"""
|
|
217
|
+
if not isinstance(si, (int, float)):
|
|
218
|
+
raise ValueError(f"Sense index must be numeric, got {type(si).__name__}")
|
|
219
|
+
|
|
220
|
+
if math.isnan(si):
|
|
221
|
+
raise ValueError("Sense index cannot be NaN")
|
|
222
|
+
|
|
223
|
+
if math.isinf(si):
|
|
224
|
+
raise ValueError("Sense index cannot be infinite")
|
|
225
|
+
|
|
226
|
+
if si < 0:
|
|
227
|
+
raise ValueError(f"Sense index must be non-negative, got {si}")
|
|
228
|
+
|
|
229
|
+
return float(si)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def validate_nodal_input(data: dict[str, Any]) -> dict[str, Any]:
|
|
233
|
+
"""Validate a complete nodal data structure.
|
|
234
|
+
|
|
235
|
+
This function validates all common NFR node attributes to ensure
|
|
236
|
+
structural coherence before database persistence.
|
|
237
|
+
|
|
238
|
+
Parameters
|
|
239
|
+
----------
|
|
240
|
+
data : dict
|
|
241
|
+
Dictionary containing nodal attributes
|
|
242
|
+
|
|
243
|
+
Returns
|
|
244
|
+
-------
|
|
245
|
+
dict
|
|
246
|
+
The validated data dictionary
|
|
247
|
+
|
|
248
|
+
Raises
|
|
249
|
+
------
|
|
250
|
+
ValueError
|
|
251
|
+
If any attribute fails validation
|
|
252
|
+
|
|
253
|
+
Example
|
|
254
|
+
-------
|
|
255
|
+
>>> data = {"nu_f": 0.5, "phase": 1.57, "delta_nfr": 0.1}
|
|
256
|
+
>>> validated = validate_nodal_input(data)
|
|
257
|
+
>>> validated["nu_f"]
|
|
258
|
+
0.5
|
|
259
|
+
"""
|
|
260
|
+
validated = {}
|
|
261
|
+
|
|
262
|
+
if "nu_f" in data:
|
|
263
|
+
validated["nu_f"] = validate_structural_frequency(data["nu_f"])
|
|
264
|
+
|
|
265
|
+
if "phase" in data:
|
|
266
|
+
validated["phase"] = validate_phase_value(data["phase"])
|
|
267
|
+
|
|
268
|
+
if "coherence" in data:
|
|
269
|
+
validated["coherence"] = validate_coherence_value(data["coherence"])
|
|
270
|
+
|
|
271
|
+
if "si" in data or "sense_index" in data:
|
|
272
|
+
si_key = "si" if "si" in data else "sense_index"
|
|
273
|
+
validated[si_key] = validate_sense_index(data[si_key])
|
|
274
|
+
|
|
275
|
+
# Pass through other fields without validation
|
|
276
|
+
# (e.g., node_id, epi arrays, etc.)
|
|
277
|
+
for key, value in data.items():
|
|
278
|
+
if key not in validated:
|
|
279
|
+
validated[key] = value
|
|
280
|
+
|
|
281
|
+
return validated
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
__all__ = (
|
|
285
|
+
"validate_coherence_value",
|
|
286
|
+
"validate_nodal_input",
|
|
287
|
+
"validate_phase_value",
|
|
288
|
+
"validate_sense_index",
|
|
289
|
+
"validate_structural_frequency",
|
|
290
|
+
)
|
tnfr/selector.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""Utilities to select structural operator symbols based on structural metrics.
|
|
2
|
+
|
|
3
|
+
This module normalises thresholds, computes selection scores and applies
|
|
4
|
+
hysteresis when assigning structural operator symbols (glyphs) to nodes.
|
|
5
|
+
|
|
6
|
+
Each structural operator (Emission, Reception, Coherence, etc.) is represented
|
|
7
|
+
by a glyph symbol (AL, EN, IL, etc.) that this module selects based on the
|
|
8
|
+
node's current structural state.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import threading
|
|
14
|
+
from operator import itemgetter
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Mapping, cast
|
|
16
|
+
from weakref import WeakKeyDictionary
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
19
|
+
import networkx as nx
|
|
20
|
+
|
|
21
|
+
from .constants import DEFAULTS
|
|
22
|
+
from .config.defaults_core import SELECTOR_THRESHOLD_DEFAULTS
|
|
23
|
+
from .utils import clamp01
|
|
24
|
+
from .metrics.common import compute_dnfr_accel_max
|
|
25
|
+
from .types import SelectorNorms, SelectorThresholds, SelectorWeights
|
|
26
|
+
from .utils import is_non_string_sequence
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
29
|
+
from .types import TNFRGraph
|
|
30
|
+
|
|
31
|
+
HYSTERESIS_GLYPHS: set[str] = {"IL", "OZ", "ZHIR", "THOL", "NAV", "RA"}
|
|
32
|
+
|
|
33
|
+
__all__ = (
|
|
34
|
+
"_selector_thresholds",
|
|
35
|
+
"_selector_norms",
|
|
36
|
+
"_calc_selector_score",
|
|
37
|
+
"_apply_selector_hysteresis",
|
|
38
|
+
"_selector_parallel_jobs",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_SelectorThresholdItems = tuple[tuple[str, float], ...]
|
|
42
|
+
_SelectorThresholdCacheEntry = tuple[
|
|
43
|
+
_SelectorThresholdItems,
|
|
44
|
+
SelectorThresholds,
|
|
45
|
+
]
|
|
46
|
+
_SELECTOR_THRESHOLD_CACHE: WeakKeyDictionary[
|
|
47
|
+
"nx.Graph",
|
|
48
|
+
_SelectorThresholdCacheEntry,
|
|
49
|
+
] = WeakKeyDictionary()
|
|
50
|
+
_SELECTOR_THRESHOLD_CACHE_LOCK = threading.Lock()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _sorted_items(mapping: Mapping[str, float]) -> _SelectorThresholdItems:
|
|
54
|
+
"""Return mapping items sorted by key.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
mapping : Mapping[str, float]
|
|
59
|
+
Mapping whose items will be sorted.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
tuple[tuple[str, float], ...]
|
|
64
|
+
Key-sorted items providing a hashable representation for memoisation.
|
|
65
|
+
"""
|
|
66
|
+
return tuple(sorted(mapping.items()))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _compute_selector_thresholds(
|
|
70
|
+
thr_sel_items: _SelectorThresholdItems,
|
|
71
|
+
) -> SelectorThresholds:
|
|
72
|
+
"""Construct selector thresholds for a graph.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
thr_sel_items : tuple[tuple[str, float], ...]
|
|
77
|
+
Selector threshold items as ``(key, value)`` pairs.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
dict[str, float]
|
|
82
|
+
Normalised thresholds for selector metrics.
|
|
83
|
+
"""
|
|
84
|
+
thr_sel = dict(thr_sel_items)
|
|
85
|
+
|
|
86
|
+
out: dict[str, float] = {}
|
|
87
|
+
for key, default in SELECTOR_THRESHOLD_DEFAULTS.items():
|
|
88
|
+
val = thr_sel.get(key, default)
|
|
89
|
+
out[key] = clamp01(float(val))
|
|
90
|
+
return cast(SelectorThresholds, out)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _selector_thresholds(G: "nx.Graph") -> SelectorThresholds:
|
|
94
|
+
"""Return normalised thresholds for Si, ΔNFR and acceleration.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
G : nx.Graph
|
|
99
|
+
Graph whose configuration stores selector thresholds.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
dict[str, float]
|
|
104
|
+
Dictionary with clamped hi/lo thresholds, memoised per graph.
|
|
105
|
+
"""
|
|
106
|
+
sel_defaults = DEFAULTS.get("SELECTOR_THRESHOLDS", {})
|
|
107
|
+
thr_sel = {**sel_defaults, **G.graph.get("SELECTOR_THRESHOLDS", {})}
|
|
108
|
+
thr_sel_items = _sorted_items(thr_sel)
|
|
109
|
+
|
|
110
|
+
with _SELECTOR_THRESHOLD_CACHE_LOCK:
|
|
111
|
+
cached = _SELECTOR_THRESHOLD_CACHE.get(G)
|
|
112
|
+
if cached is not None and cached[0] == thr_sel_items:
|
|
113
|
+
return cached[1]
|
|
114
|
+
|
|
115
|
+
thresholds = _compute_selector_thresholds(thr_sel_items)
|
|
116
|
+
|
|
117
|
+
with _SELECTOR_THRESHOLD_CACHE_LOCK:
|
|
118
|
+
cached = _SELECTOR_THRESHOLD_CACHE.get(G)
|
|
119
|
+
if cached is not None and cached[0] == thr_sel_items:
|
|
120
|
+
return cached[1]
|
|
121
|
+
_SELECTOR_THRESHOLD_CACHE[G] = (thr_sel_items, thresholds)
|
|
122
|
+
return thresholds
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _selector_norms(G: "nx.Graph") -> SelectorNorms:
|
|
126
|
+
"""Compute and cache selector norms for ΔNFR and acceleration.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
G : nx.Graph
|
|
131
|
+
Graph for which to compute maxima. Results are stored in ``G.graph``
|
|
132
|
+
under ``"_sel_norms"``.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
dict
|
|
137
|
+
Mapping with normalisation maxima for ``dnfr`` and ``accel``.
|
|
138
|
+
"""
|
|
139
|
+
norms = compute_dnfr_accel_max(G)
|
|
140
|
+
G.graph["_sel_norms"] = norms
|
|
141
|
+
return norms
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _calc_selector_score(
|
|
145
|
+
Si: float, dnfr: float, accel: float, weights: SelectorWeights
|
|
146
|
+
) -> float:
|
|
147
|
+
"""Compute weighted selector score.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
Si : float
|
|
152
|
+
Normalised sense index.
|
|
153
|
+
dnfr : float
|
|
154
|
+
Normalised absolute ΔNFR value.
|
|
155
|
+
accel : float
|
|
156
|
+
Normalised acceleration (|d²EPI/dt²|).
|
|
157
|
+
weights : dict[str, float]
|
|
158
|
+
Normalised weights for ``"w_si"``, ``"w_dnfr"`` and ``"w_accel"``.
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
float
|
|
163
|
+
Final weighted score.
|
|
164
|
+
"""
|
|
165
|
+
return (
|
|
166
|
+
weights["w_si"] * Si
|
|
167
|
+
+ weights["w_dnfr"] * (1.0 - dnfr)
|
|
168
|
+
+ weights["w_accel"] * (1.0 - accel)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _apply_selector_hysteresis(
|
|
173
|
+
nd: dict[str, Any],
|
|
174
|
+
Si: float,
|
|
175
|
+
dnfr: float,
|
|
176
|
+
accel: float,
|
|
177
|
+
thr: dict[str, float],
|
|
178
|
+
margin: float | None,
|
|
179
|
+
) -> str | None:
|
|
180
|
+
"""Apply hysteresis when values are near thresholds.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
nd : dict[str, Any]
|
|
185
|
+
Node attribute dictionary containing glyph history.
|
|
186
|
+
Si : float
|
|
187
|
+
Normalised sense index.
|
|
188
|
+
dnfr : float
|
|
189
|
+
Normalised absolute ΔNFR value.
|
|
190
|
+
accel : float
|
|
191
|
+
Normalised acceleration.
|
|
192
|
+
thr : dict[str, float]
|
|
193
|
+
Thresholds returned by :func:`_selector_thresholds`.
|
|
194
|
+
margin : float or None
|
|
195
|
+
When positive, distance from thresholds below which the previous
|
|
196
|
+
glyph is reused. Falsy margins disable hysteresis entirely, letting
|
|
197
|
+
selectors bypass the reuse logic.
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
str or None
|
|
202
|
+
Previous glyph if hysteresis applies, otherwise ``None``.
|
|
203
|
+
"""
|
|
204
|
+
# Batch extraction reduces dictionary lookups inside loops.
|
|
205
|
+
if not margin:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
si_hi, si_lo, dnfr_hi, dnfr_lo, accel_hi, accel_lo = itemgetter(
|
|
209
|
+
"si_hi", "si_lo", "dnfr_hi", "dnfr_lo", "accel_hi", "accel_lo"
|
|
210
|
+
)(thr)
|
|
211
|
+
|
|
212
|
+
d_si = min(abs(Si - si_hi), abs(Si - si_lo))
|
|
213
|
+
d_dn = min(abs(dnfr - dnfr_hi), abs(dnfr - dnfr_lo))
|
|
214
|
+
d_ac = min(abs(accel - accel_hi), abs(accel - accel_lo))
|
|
215
|
+
certeza = min(d_si, d_dn, d_ac)
|
|
216
|
+
if certeza < margin:
|
|
217
|
+
hist = nd.get("glyph_history")
|
|
218
|
+
if not is_non_string_sequence(hist) or not hist:
|
|
219
|
+
return None
|
|
220
|
+
prev = hist[-1]
|
|
221
|
+
if isinstance(prev, str) and prev in HYSTERESIS_GLYPHS:
|
|
222
|
+
return prev
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _selector_parallel_jobs(G: "TNFRGraph") -> int | None:
|
|
227
|
+
"""Return worker count for selector helpers when parallelism is enabled.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
G : TNFRGraph
|
|
232
|
+
Graph containing selector configuration.
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
int | None
|
|
237
|
+
Number of parallel jobs to use, or None if parallelism is disabled
|
|
238
|
+
or invalid configuration is provided.
|
|
239
|
+
"""
|
|
240
|
+
raw_jobs = G.graph.get("GLYPH_SELECTOR_N_JOBS")
|
|
241
|
+
try:
|
|
242
|
+
n_jobs = None if raw_jobs is None else int(raw_jobs)
|
|
243
|
+
except (TypeError, ValueError):
|
|
244
|
+
return None
|
|
245
|
+
if n_jobs is None or n_jobs <= 1:
|
|
246
|
+
return None
|
|
247
|
+
return n_jobs
|
tnfr/selector.pyi
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
__all__: Any
|
|
6
|
+
|
|
7
|
+
def __getattr__(name: str) -> Any: ...
|
|
8
|
+
def _apply_selector_hysteresis(
|
|
9
|
+
nd: dict[str, Any],
|
|
10
|
+
Si: float,
|
|
11
|
+
dnfr: float,
|
|
12
|
+
accel: float,
|
|
13
|
+
thr: Mapping[str, float],
|
|
14
|
+
margin: float | None,
|
|
15
|
+
) -> str | None: ...
|
|
16
|
+
|
|
17
|
+
_calc_selector_score: Any
|
|
18
|
+
_selector_norms: Any
|
|
19
|
+
_selector_thresholds: Any
|