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,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from numpy.random import Generator
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
__all__ = ["build_delta_nfr", "build_lindblad_delta_nfr"]
|
|
8
|
+
|
|
9
|
+
def build_delta_nfr(
|
|
10
|
+
dim: int,
|
|
11
|
+
*,
|
|
12
|
+
topology: str = "laplacian",
|
|
13
|
+
nu_f: float = 1.0,
|
|
14
|
+
scale: float = 1.0,
|
|
15
|
+
rng: Generator | None = None,
|
|
16
|
+
) -> np.ndarray: ...
|
|
17
|
+
def build_lindblad_delta_nfr(
|
|
18
|
+
*,
|
|
19
|
+
hamiltonian: Sequence[Sequence[complex]] | np.ndarray | None = None,
|
|
20
|
+
collapse_operators: (
|
|
21
|
+
Sequence[Sequence[Sequence[complex]] | np.ndarray] | None
|
|
22
|
+
) = None,
|
|
23
|
+
dim: int | None = None,
|
|
24
|
+
nu_f: float = 1.0,
|
|
25
|
+
scale: float = 1.0,
|
|
26
|
+
ensure_trace_preserving: bool = True,
|
|
27
|
+
ensure_contractive: bool = True,
|
|
28
|
+
atol: float = 1e-09,
|
|
29
|
+
) -> np.ndarray: ...
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Structural metrics preserving TNFR coherence invariants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .operators import CoherenceOperator
|
|
10
|
+
|
|
11
|
+
__all__ = ["dcoh"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _as_coherent_vector(
|
|
15
|
+
state: Sequence[complex] | np.ndarray,
|
|
16
|
+
*,
|
|
17
|
+
dimension: int,
|
|
18
|
+
) -> np.ndarray:
|
|
19
|
+
"""Return a complex vector compatible with ``CoherenceOperator`` matrices."""
|
|
20
|
+
|
|
21
|
+
vector = np.asarray(state, dtype=np.complex128)
|
|
22
|
+
if vector.ndim != 1 or vector.shape[0] != dimension:
|
|
23
|
+
raise ValueError(
|
|
24
|
+
"State vector dimension mismatch: "
|
|
25
|
+
f"expected ({dimension},), received {vector.shape!r}."
|
|
26
|
+
)
|
|
27
|
+
return vector
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _normalise_vector(
|
|
31
|
+
vector: np.ndarray,
|
|
32
|
+
*,
|
|
33
|
+
atol: float,
|
|
34
|
+
label: str,
|
|
35
|
+
) -> np.ndarray:
|
|
36
|
+
norm = np.linalg.norm(vector)
|
|
37
|
+
if np.isclose(norm, 0.0, atol=atol):
|
|
38
|
+
raise ValueError(f"Cannot normalise null coherence state {label}.")
|
|
39
|
+
return vector / norm
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def dcoh(
|
|
43
|
+
psi1: Sequence[complex] | np.ndarray,
|
|
44
|
+
psi2: Sequence[complex] | np.ndarray,
|
|
45
|
+
operator: CoherenceOperator,
|
|
46
|
+
*,
|
|
47
|
+
normalise: bool = True,
|
|
48
|
+
atol: float = 1e-9,
|
|
49
|
+
) -> float:
|
|
50
|
+
"""Return the TNFR dissimilarity of coherence between ``psi1`` and ``psi2``.
|
|
51
|
+
|
|
52
|
+
The metric follows the canonical TNFR expectation contracts:
|
|
53
|
+
|
|
54
|
+
* States are converted to Hilbert-compatible complex vectors respecting the
|
|
55
|
+
``CoherenceOperator`` dimension, preserving the spectral phase space.
|
|
56
|
+
* Optional normalisation keeps overlap and expectations coherent with
|
|
57
|
+
unit-phase contracts, preventing coherence inflation.
|
|
58
|
+
* Expectation values ``⟨ψ|Ĉ|ψ⟩`` must remain strictly positive; null or
|
|
59
|
+
negative projections signal a collapse and therefore raise ``ValueError``.
|
|
60
|
+
|
|
61
|
+
Parameters mirror the runtime helpers so callers can rely on the same
|
|
62
|
+
tolerances. Numerical overflow is contained by bounding intermediate ratios
|
|
63
|
+
within ``[0, 1]`` up to ``atol`` before applying the Bures-style angle
|
|
64
|
+
``arccos(√ratio)``, ensuring the returned dissimilarity remains within the
|
|
65
|
+
TNFR coherence interval.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
dimension = operator.matrix.shape[0]
|
|
69
|
+
vector1 = _as_coherent_vector(psi1, dimension=dimension)
|
|
70
|
+
vector2 = _as_coherent_vector(psi2, dimension=dimension)
|
|
71
|
+
|
|
72
|
+
if normalise:
|
|
73
|
+
vector1_norm = _normalise_vector(vector1, atol=atol, label="ψ₁")
|
|
74
|
+
vector2_norm = _normalise_vector(vector2, atol=atol, label="ψ₂")
|
|
75
|
+
else:
|
|
76
|
+
vector1_norm = vector1
|
|
77
|
+
vector2_norm = vector2
|
|
78
|
+
|
|
79
|
+
weighted_vector2 = operator.matrix @ vector2_norm
|
|
80
|
+
if weighted_vector2.shape != vector2_norm.shape:
|
|
81
|
+
raise ValueError("Operator application distorted coherence dimensionality.")
|
|
82
|
+
|
|
83
|
+
cross = np.vdot(vector1_norm, weighted_vector2)
|
|
84
|
+
if not np.isfinite(cross):
|
|
85
|
+
raise ValueError("State overlap produced a non-finite value.")
|
|
86
|
+
|
|
87
|
+
expect1 = float(operator.expectation(vector1, normalise=normalise, atol=atol))
|
|
88
|
+
expect2 = float(operator.expectation(vector2, normalise=normalise, atol=atol))
|
|
89
|
+
|
|
90
|
+
for idx, value in enumerate((expect1, expect2), start=1):
|
|
91
|
+
if not np.isfinite(value):
|
|
92
|
+
raise ValueError(f"Coherence expectation diverged for state ψ{idx}.")
|
|
93
|
+
if value <= 0.0 or np.isclose(value, 0.0, atol=atol):
|
|
94
|
+
raise ValueError(
|
|
95
|
+
"Coherence expectation must remain strictly positive to"
|
|
96
|
+
f" preserve TNFR invariants (state ψ{idx})."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
denominator = expect1 * expect2
|
|
100
|
+
if not np.isfinite(denominator):
|
|
101
|
+
raise ValueError("Coherence expectations produced a non-finite product.")
|
|
102
|
+
if denominator <= 0.0 or np.isclose(denominator, 0.0, atol=atol):
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"Product of coherence expectations must be strictly positive to"
|
|
105
|
+
" evaluate dissimilarity."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
ratio = (np.abs(cross) ** 2) / denominator
|
|
109
|
+
eps = max(np.finfo(float).eps * 10.0, atol)
|
|
110
|
+
if ratio < -eps:
|
|
111
|
+
raise ValueError("Overlap produced a negative coherence ratio.")
|
|
112
|
+
if ratio < 0.0:
|
|
113
|
+
ratio = 0.0
|
|
114
|
+
if ratio > 1.0 + eps:
|
|
115
|
+
raise ValueError("Coherence ratio exceeded unity beyond tolerance.")
|
|
116
|
+
if ratio > 1.0:
|
|
117
|
+
ratio = 1.0
|
|
118
|
+
|
|
119
|
+
return float(np.arccos(np.sqrt(ratio)))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .operators import CoherenceOperator
|
|
5
|
+
from typing import Sequence
|
|
6
|
+
|
|
7
|
+
__all__ = ["dcoh"]
|
|
8
|
+
|
|
9
|
+
def dcoh(
|
|
10
|
+
psi1: Sequence[complex] | np.ndarray,
|
|
11
|
+
psi2: Sequence[complex] | np.ndarray,
|
|
12
|
+
operator: CoherenceOperator,
|
|
13
|
+
*,
|
|
14
|
+
normalise: bool = True,
|
|
15
|
+
atol: float = 1e-09,
|
|
16
|
+
) -> float: ...
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Spectral operators modelling coherence and frequency dynamics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import field
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..compat.dataclass import dataclass
|
|
11
|
+
from .backend import MathematicsBackend, ensure_array, ensure_numpy, get_backend
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING: # pragma: no cover - typing imports only
|
|
14
|
+
import numpy.typing as npt
|
|
15
|
+
|
|
16
|
+
ComplexVector = npt.NDArray[np.complexfloating[np.float64, np.float64]]
|
|
17
|
+
ComplexMatrix = npt.NDArray[np.complexfloating[np.float64, np.float64]]
|
|
18
|
+
else: # pragma: no cover - runtime alias
|
|
19
|
+
ComplexVector = np.ndarray
|
|
20
|
+
ComplexMatrix = np.ndarray
|
|
21
|
+
|
|
22
|
+
__all__ = ["CoherenceOperator", "FrequencyOperator"]
|
|
23
|
+
|
|
24
|
+
DEFAULT_C_MIN: float = 0.1
|
|
25
|
+
_C_MIN_UNSET = object()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _as_complex_vector(
|
|
29
|
+
vector: Sequence[complex] | np.ndarray | Any,
|
|
30
|
+
*,
|
|
31
|
+
backend: MathematicsBackend,
|
|
32
|
+
) -> Any:
|
|
33
|
+
arr = ensure_array(vector, dtype=np.complex128, backend=backend)
|
|
34
|
+
if getattr(arr, "ndim", len(getattr(arr, "shape", ()))) != 1:
|
|
35
|
+
raise ValueError("Vector input must be one-dimensional.")
|
|
36
|
+
return arr
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _as_complex_matrix(
|
|
40
|
+
matrix: Sequence[Sequence[complex]] | np.ndarray | Any,
|
|
41
|
+
*,
|
|
42
|
+
backend: MathematicsBackend,
|
|
43
|
+
) -> Any:
|
|
44
|
+
arr = ensure_array(matrix, dtype=np.complex128, backend=backend)
|
|
45
|
+
shape = getattr(arr, "shape", None)
|
|
46
|
+
if shape is None or len(shape) != 2 or shape[0] != shape[1]:
|
|
47
|
+
raise ValueError("Operator matrix must be square.")
|
|
48
|
+
return arr
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _make_diagonal(values: Any, *, backend: MathematicsBackend) -> Any:
|
|
52
|
+
dim = int(getattr(values, "shape")[0])
|
|
53
|
+
identity = ensure_array(np.eye(dim, dtype=np.complex128), backend=backend)
|
|
54
|
+
return backend.einsum("i,ij->ij", values, identity)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(slots=True)
|
|
58
|
+
class CoherenceOperator:
|
|
59
|
+
"""Hermitian operator capturing coherence redistribution.
|
|
60
|
+
|
|
61
|
+
The operator encapsulates how a TNFR EPI redistributes coherence across
|
|
62
|
+
its spectral components. It supports construction either from an explicit
|
|
63
|
+
matrix expressed on the canonical basis or from a pre-computed list of
|
|
64
|
+
eigenvalues (interpreted as already diagonalised). The minimal eigenvalue
|
|
65
|
+
``c_min`` is tracked explicitly so structural stability thresholds are easy
|
|
66
|
+
to evaluate during simulations. The precedence for determining the stored
|
|
67
|
+
threshold is: an explicit ``c_min`` wins, otherwise the spectral floor
|
|
68
|
+
(minimum real eigenvalue) is used, with ``0.1`` acting as the canonical
|
|
69
|
+
fallback for callers that still wish to supply a fixed number.
|
|
70
|
+
|
|
71
|
+
When instantiated under an automatic differentiation backend (JAX, PyTorch)
|
|
72
|
+
the spectral decomposition remains differentiable provided the supplied
|
|
73
|
+
operator is non-defective. NumPy callers receive ``numpy.ndarray`` outputs
|
|
74
|
+
and all tolerance checks match the historical semantics.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
matrix: ComplexMatrix
|
|
78
|
+
eigenvalues: ComplexVector
|
|
79
|
+
c_min: float
|
|
80
|
+
backend: MathematicsBackend = field(init=False, repr=False)
|
|
81
|
+
_matrix_backend: Any = field(init=False, repr=False)
|
|
82
|
+
_eigenvalues_backend: Any = field(init=False, repr=False)
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
operator: Sequence[Sequence[complex]] | Sequence[complex] | np.ndarray | Any,
|
|
87
|
+
*,
|
|
88
|
+
c_min: float | object = _C_MIN_UNSET,
|
|
89
|
+
ensure_hermitian: bool = True,
|
|
90
|
+
atol: float = 1e-9,
|
|
91
|
+
backend: MathematicsBackend | None = None,
|
|
92
|
+
) -> None:
|
|
93
|
+
resolved_backend = backend or get_backend()
|
|
94
|
+
operand = ensure_array(operator, dtype=np.complex128, backend=resolved_backend)
|
|
95
|
+
if getattr(operand, "ndim", len(getattr(operand, "shape", ()))) == 1:
|
|
96
|
+
eigvals_backend = _as_complex_vector(operand, backend=resolved_backend)
|
|
97
|
+
if ensure_hermitian:
|
|
98
|
+
imag = ensure_numpy(eigvals_backend.imag, backend=resolved_backend)
|
|
99
|
+
if not np.allclose(imag, 0.0, atol=atol):
|
|
100
|
+
raise ValueError("Hermitian operators require real eigenvalues.")
|
|
101
|
+
matrix_backend = _make_diagonal(eigvals_backend, backend=resolved_backend)
|
|
102
|
+
eigenvalues_backend = eigvals_backend
|
|
103
|
+
else:
|
|
104
|
+
matrix_backend = _as_complex_matrix(operand, backend=resolved_backend)
|
|
105
|
+
if ensure_hermitian and not self._check_hermitian(
|
|
106
|
+
matrix_backend, atol=atol, backend=resolved_backend
|
|
107
|
+
):
|
|
108
|
+
raise ValueError("Coherence operator must be Hermitian.")
|
|
109
|
+
if ensure_hermitian:
|
|
110
|
+
eigenvalues_backend, _ = resolved_backend.eigh(matrix_backend)
|
|
111
|
+
else:
|
|
112
|
+
eigenvalues_backend, _ = resolved_backend.eig(matrix_backend)
|
|
113
|
+
|
|
114
|
+
self.backend = resolved_backend
|
|
115
|
+
self._matrix_backend = matrix_backend
|
|
116
|
+
self._eigenvalues_backend = eigenvalues_backend
|
|
117
|
+
self.matrix = ensure_numpy(matrix_backend, backend=resolved_backend)
|
|
118
|
+
self.eigenvalues = ensure_numpy(eigenvalues_backend, backend=resolved_backend)
|
|
119
|
+
derived_c_min = float(np.min(self.eigenvalues.real))
|
|
120
|
+
if c_min is _C_MIN_UNSET:
|
|
121
|
+
self.c_min = derived_c_min
|
|
122
|
+
else:
|
|
123
|
+
self.c_min = float(c_min)
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def _check_hermitian(
|
|
127
|
+
matrix: Any,
|
|
128
|
+
*,
|
|
129
|
+
atol: float = 1e-9,
|
|
130
|
+
backend: MathematicsBackend,
|
|
131
|
+
) -> bool:
|
|
132
|
+
matrix_np = ensure_numpy(matrix, backend=backend)
|
|
133
|
+
return bool(np.allclose(matrix_np, matrix_np.conj().T, atol=atol))
|
|
134
|
+
|
|
135
|
+
def is_hermitian(self, *, atol: float = 1e-9) -> bool:
|
|
136
|
+
"""Return ``True`` when the operator matches its adjoint."""
|
|
137
|
+
|
|
138
|
+
return self._check_hermitian(
|
|
139
|
+
self._matrix_backend, atol=atol, backend=self.backend
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def is_positive_semidefinite(self, *, atol: float = 1e-9) -> bool:
|
|
143
|
+
"""Check that all eigenvalues are non-negative within ``atol``."""
|
|
144
|
+
|
|
145
|
+
return bool(np.all(self.eigenvalues.real >= -atol))
|
|
146
|
+
|
|
147
|
+
def spectrum(self) -> ComplexVector:
|
|
148
|
+
"""Return the complex eigenvalue spectrum."""
|
|
149
|
+
|
|
150
|
+
return np.asarray(self.eigenvalues, dtype=np.complex128)
|
|
151
|
+
|
|
152
|
+
def spectral_radius(self) -> float:
|
|
153
|
+
"""Return the largest magnitude eigenvalue (spectral radius)."""
|
|
154
|
+
|
|
155
|
+
return float(np.max(np.abs(self.eigenvalues)))
|
|
156
|
+
|
|
157
|
+
def spectral_bandwidth(self) -> float:
|
|
158
|
+
"""Return the real bandwidth ``max(λ) - min(λ)``."""
|
|
159
|
+
|
|
160
|
+
eigvals = self.eigenvalues.real
|
|
161
|
+
return float(np.max(eigvals) - np.min(eigvals))
|
|
162
|
+
|
|
163
|
+
def expectation(
|
|
164
|
+
self,
|
|
165
|
+
state: Sequence[complex] | np.ndarray,
|
|
166
|
+
*,
|
|
167
|
+
normalise: bool = True,
|
|
168
|
+
atol: float = 1e-9,
|
|
169
|
+
) -> float:
|
|
170
|
+
vector_backend = _as_complex_vector(state, backend=self.backend)
|
|
171
|
+
if vector_backend.shape != (self.matrix.shape[0],):
|
|
172
|
+
raise ValueError("State vector dimension mismatch with operator.")
|
|
173
|
+
working = vector_backend
|
|
174
|
+
if normalise:
|
|
175
|
+
norm_value = ensure_numpy(self.backend.norm(working), backend=self.backend)
|
|
176
|
+
norm = float(norm_value)
|
|
177
|
+
if np.isclose(norm, 0.0):
|
|
178
|
+
raise ValueError("Cannot normalise a null state vector.")
|
|
179
|
+
working = working / norm
|
|
180
|
+
column = working[..., None]
|
|
181
|
+
bra = self.backend.conjugate_transpose(column)
|
|
182
|
+
evolved = self.backend.matmul(self._matrix_backend, column)
|
|
183
|
+
expectation_backend = self.backend.matmul(bra, evolved)
|
|
184
|
+
expectation = ensure_numpy(expectation_backend, backend=self.backend)
|
|
185
|
+
expectation_scalar = complex(np.asarray(expectation).reshape(()))
|
|
186
|
+
if abs(expectation_scalar.imag) > atol:
|
|
187
|
+
raise ValueError(
|
|
188
|
+
"Expectation value carries an imaginary component beyond tolerance."
|
|
189
|
+
)
|
|
190
|
+
eps = np.finfo(float).eps
|
|
191
|
+
tol = max(1000.0, float(atol / eps)) if atol > 0 else 1000.0
|
|
192
|
+
real_expectation = np.real_if_close(expectation_scalar, tol=tol)
|
|
193
|
+
if np.iscomplexobj(real_expectation):
|
|
194
|
+
raise ValueError("Expectation remained complex after coercion.")
|
|
195
|
+
return float(real_expectation)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class FrequencyOperator(CoherenceOperator):
|
|
199
|
+
"""Operator encoding the structural frequency distribution.
|
|
200
|
+
|
|
201
|
+
The frequency operator reuses the coherence machinery but enforces a real
|
|
202
|
+
spectrum representing the structural hertz (νf) each mode contributes. Its
|
|
203
|
+
helpers therefore constrain outputs to the real axis and expose projections
|
|
204
|
+
suited for telemetry collection.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(
|
|
208
|
+
self,
|
|
209
|
+
operator: Sequence[Sequence[complex]] | Sequence[complex] | np.ndarray | Any,
|
|
210
|
+
*,
|
|
211
|
+
ensure_hermitian: bool = True,
|
|
212
|
+
atol: float = 1e-9,
|
|
213
|
+
backend: MathematicsBackend | None = None,
|
|
214
|
+
) -> None:
|
|
215
|
+
super().__init__(
|
|
216
|
+
operator,
|
|
217
|
+
ensure_hermitian=ensure_hermitian,
|
|
218
|
+
atol=atol,
|
|
219
|
+
backend=backend,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def spectrum(self) -> np.ndarray:
|
|
223
|
+
"""Return the real-valued structural frequency spectrum."""
|
|
224
|
+
|
|
225
|
+
return np.asarray(self.eigenvalues.real, dtype=float)
|
|
226
|
+
|
|
227
|
+
def is_positive_semidefinite(self, *, atol: float = 1e-9) -> bool:
|
|
228
|
+
"""Frequency spectra must be non-negative to preserve νf semantics."""
|
|
229
|
+
|
|
230
|
+
return bool(np.all(self.spectrum() >= -atol))
|
|
231
|
+
|
|
232
|
+
def project_frequency(
|
|
233
|
+
self,
|
|
234
|
+
state: Sequence[complex] | np.ndarray,
|
|
235
|
+
*,
|
|
236
|
+
normalise: bool = True,
|
|
237
|
+
atol: float = 1e-9,
|
|
238
|
+
) -> float:
|
|
239
|
+
return self.expectation(state, normalise=normalise, atol=atol)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .backend import MathematicsBackend
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Sequence
|
|
7
|
+
import numpy.typing as npt
|
|
8
|
+
|
|
9
|
+
__all__ = ["CoherenceOperator", "FrequencyOperator"]
|
|
10
|
+
|
|
11
|
+
ComplexMatrix = npt.NDArray[np.complexfloating[np.float64, np.float64]]
|
|
12
|
+
ComplexVector = npt.NDArray[np.complexfloating[np.float64, np.float64]]
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class CoherenceOperator:
|
|
16
|
+
matrix: ComplexMatrix
|
|
17
|
+
eigenvalues: ComplexVector
|
|
18
|
+
c_min: float
|
|
19
|
+
backend: MathematicsBackend = field(init=False, repr=False)
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
operator: Sequence[Sequence[complex]] | Sequence[complex] | np.ndarray | Any,
|
|
23
|
+
*,
|
|
24
|
+
c_min: float | object = ...,
|
|
25
|
+
ensure_hermitian: bool = True,
|
|
26
|
+
atol: float = 1e-09,
|
|
27
|
+
backend: MathematicsBackend | None = None,
|
|
28
|
+
) -> None: ...
|
|
29
|
+
def is_hermitian(self, *, atol: float = 1e-09) -> bool: ...
|
|
30
|
+
def is_positive_semidefinite(self, *, atol: float = 1e-09) -> bool: ...
|
|
31
|
+
def spectrum(self) -> ComplexVector: ...
|
|
32
|
+
def spectral_radius(self) -> float: ...
|
|
33
|
+
def spectral_bandwidth(self) -> float: ...
|
|
34
|
+
def expectation(
|
|
35
|
+
self,
|
|
36
|
+
state: Sequence[complex] | np.ndarray,
|
|
37
|
+
*,
|
|
38
|
+
normalise: bool = True,
|
|
39
|
+
atol: float = 1e-09,
|
|
40
|
+
) -> float: ...
|
|
41
|
+
|
|
42
|
+
class FrequencyOperator(CoherenceOperator):
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
operator: Sequence[Sequence[complex]] | Sequence[complex] | np.ndarray | Any,
|
|
46
|
+
*,
|
|
47
|
+
ensure_hermitian: bool = True,
|
|
48
|
+
atol: float = 1e-09,
|
|
49
|
+
backend: MathematicsBackend | None = None,
|
|
50
|
+
) -> None: ...
|
|
51
|
+
def spectrum(self) -> np.ndarray: ...
|
|
52
|
+
def is_positive_semidefinite(self, *, atol: float = 1e-09) -> bool: ...
|
|
53
|
+
def project_frequency(
|
|
54
|
+
self,
|
|
55
|
+
state: Sequence[complex] | np.ndarray,
|
|
56
|
+
*,
|
|
57
|
+
normalise: bool = True,
|
|
58
|
+
atol: float = 1e-09,
|
|
59
|
+
) -> float: ...
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Factory helpers to assemble TNFR coherence and frequency operators."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .backend import ensure_array, ensure_numpy, get_backend
|
|
8
|
+
from .operators import CoherenceOperator, FrequencyOperator
|
|
9
|
+
|
|
10
|
+
__all__ = ["make_coherence_operator", "make_frequency_operator"]
|
|
11
|
+
|
|
12
|
+
_ATOL = 1e-9
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _validate_dimension(dim: int) -> int:
|
|
16
|
+
if int(dim) != dim:
|
|
17
|
+
raise ValueError("Operator dimension must be an integer.")
|
|
18
|
+
if dim <= 0:
|
|
19
|
+
raise ValueError("Operator dimension must be strictly positive.")
|
|
20
|
+
return int(dim)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def make_coherence_operator(
|
|
24
|
+
dim: int,
|
|
25
|
+
*,
|
|
26
|
+
spectrum: np.ndarray | None = None,
|
|
27
|
+
c_min: float = 0.1,
|
|
28
|
+
) -> CoherenceOperator:
|
|
29
|
+
"""Return a Hermitian positive semidefinite :class:`CoherenceOperator`.
|
|
30
|
+
|
|
31
|
+
This factory validates inputs, ensures structural invariants (Hermiticity
|
|
32
|
+
and positive semi-definiteness), and integrates with the TNFR backend
|
|
33
|
+
abstraction layer.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
dim : int
|
|
38
|
+
Dimensionality of the operator's Hilbert space. Must be positive.
|
|
39
|
+
spectrum : np.ndarray | None, optional
|
|
40
|
+
Custom eigenvalue spectrum. If None, uses uniform c_min values.
|
|
41
|
+
Must be real-valued and match dimension.
|
|
42
|
+
c_min : float, optional
|
|
43
|
+
Minimum coherence threshold for default spectrum (default: 0.1).
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
CoherenceOperator
|
|
48
|
+
Validated coherence operator with backend-native arrays.
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
ValueError
|
|
53
|
+
If dimension is invalid, spectrum has wrong shape, or operator
|
|
54
|
+
violates Hermiticity/PSD constraints.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
dimension = _validate_dimension(dim)
|
|
58
|
+
if not np.isfinite(c_min):
|
|
59
|
+
raise ValueError("Coherence threshold ``c_min`` must be finite.")
|
|
60
|
+
|
|
61
|
+
backend = get_backend()
|
|
62
|
+
|
|
63
|
+
if spectrum is None:
|
|
64
|
+
eigenvalues_backend = ensure_array(
|
|
65
|
+
np.full(dimension, float(c_min), dtype=float), backend=backend
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
eigenvalues_backend = ensure_array(
|
|
69
|
+
spectrum, dtype=np.complex128, backend=backend
|
|
70
|
+
)
|
|
71
|
+
eigenvalues_np = ensure_numpy(eigenvalues_backend, backend=backend)
|
|
72
|
+
if eigenvalues_np.ndim != 1:
|
|
73
|
+
raise ValueError("Coherence spectrum must be one-dimensional.")
|
|
74
|
+
if eigenvalues_np.shape[0] != dimension:
|
|
75
|
+
raise ValueError("Coherence spectrum size must match operator dimension.")
|
|
76
|
+
if np.any(np.abs(eigenvalues_np.imag) > _ATOL):
|
|
77
|
+
raise ValueError("Coherence spectrum must be real-valued within tolerance.")
|
|
78
|
+
eigenvalues_backend = ensure_array(
|
|
79
|
+
eigenvalues_np.real.astype(float, copy=False), backend=backend
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
operator = CoherenceOperator(eigenvalues_backend, c_min=c_min, backend=backend)
|
|
83
|
+
if not operator.is_hermitian(atol=_ATOL):
|
|
84
|
+
raise ValueError("Coherence operator must be Hermitian.")
|
|
85
|
+
if not operator.is_positive_semidefinite(atol=_ATOL):
|
|
86
|
+
raise ValueError("Coherence operator must be positive semidefinite.")
|
|
87
|
+
return operator
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def make_frequency_operator(matrix: np.ndarray) -> FrequencyOperator:
|
|
91
|
+
"""Return a Hermitian PSD :class:`FrequencyOperator` from ``matrix``.
|
|
92
|
+
|
|
93
|
+
This factory validates the input matrix for Hermiticity and constructs
|
|
94
|
+
a frequency operator that enforces positive semi-definiteness.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
matrix : np.ndarray
|
|
99
|
+
Square Hermitian matrix representing the frequency operator.
|
|
100
|
+
Must be complex128 compatible.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
FrequencyOperator
|
|
105
|
+
Validated frequency operator with backend-native arrays.
|
|
106
|
+
|
|
107
|
+
Raises
|
|
108
|
+
------
|
|
109
|
+
ValueError
|
|
110
|
+
If matrix is not square, not Hermitian, or not positive semidefinite.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
backend = get_backend()
|
|
114
|
+
array_backend = ensure_array(matrix, dtype=np.complex128, backend=backend)
|
|
115
|
+
array_np = ensure_numpy(array_backend, backend=backend)
|
|
116
|
+
if array_np.ndim != 2 or array_np.shape[0] != array_np.shape[1]:
|
|
117
|
+
raise ValueError("Frequency operator matrix must be square.")
|
|
118
|
+
if not np.allclose(array_np, array_np.conj().T, atol=_ATOL):
|
|
119
|
+
raise ValueError("Frequency operator must be Hermitian within tolerance.")
|
|
120
|
+
|
|
121
|
+
operator = FrequencyOperator(array_backend, backend=backend)
|
|
122
|
+
if not operator.is_positive_semidefinite(atol=_ATOL):
|
|
123
|
+
raise ValueError("Frequency operator must be positive semidefinite.")
|
|
124
|
+
return operator
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from .operators import CoherenceOperator, FrequencyOperator
|
|
5
|
+
|
|
6
|
+
__all__ = ["make_coherence_operator", "make_frequency_operator"]
|
|
7
|
+
|
|
8
|
+
def make_coherence_operator(
|
|
9
|
+
dim: int, *, spectrum: np.ndarray | None = None, c_min: float = 0.1
|
|
10
|
+
) -> CoherenceOperator: ...
|
|
11
|
+
def make_frequency_operator(matrix: np.ndarray) -> FrequencyOperator: ...
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Projection helpers constructing TNFR state vectors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..compat.dataclass import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING: # pragma: no cover - typing hook when numpy.typing is available
|
|
11
|
+
import numpy.typing as npt
|
|
12
|
+
|
|
13
|
+
ComplexVector = npt.NDArray[np.complexfloating[np.float64, np.float64]]
|
|
14
|
+
else: # pragma: no cover - runtime fallback without numpy.typing
|
|
15
|
+
ComplexVector = np.ndarray # type: ignore[assignment]
|
|
16
|
+
|
|
17
|
+
__all__ = ["StateProjector", "BasicStateProjector"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@runtime_checkable
|
|
21
|
+
class StateProjector(Protocol):
|
|
22
|
+
"""Protocol describing state projection callables.
|
|
23
|
+
|
|
24
|
+
Notes
|
|
25
|
+
-----
|
|
26
|
+
Marked with @runtime_checkable to enable isinstance() checks for validating
|
|
27
|
+
state projector implementations conform to the expected callable interface.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __call__(
|
|
31
|
+
self,
|
|
32
|
+
epi: float,
|
|
33
|
+
nu_f: float,
|
|
34
|
+
theta: float,
|
|
35
|
+
dim: int,
|
|
36
|
+
rng: np.random.Generator | None = None,
|
|
37
|
+
) -> ComplexVector:
|
|
38
|
+
"""Return a normalised TNFR state vector for the provided parameters."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(slots=True)
|
|
42
|
+
class BasicStateProjector:
|
|
43
|
+
"""Canonical projector building deterministic TNFR state vectors.
|
|
44
|
+
|
|
45
|
+
The projector maps the structural scalars of a node—its EPI magnitude,
|
|
46
|
+
structural frequency ``νf`` and phase ``θ``—onto the canonical Hilbert
|
|
47
|
+
basis. The resulting vector encodes a coherent amplitude envelope derived
|
|
48
|
+
from the structural intensity while the complex exponential captures the
|
|
49
|
+
phase progression across the local modes. Optional stochastic excitation is
|
|
50
|
+
injected via a :class:`numpy.random.Generator` to model controlled
|
|
51
|
+
dissonance while preserving determinism when a seed is provided.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
dtype: np.dtype[np.complexfloating[np.float64, np.float64]] = np.dtype(
|
|
55
|
+
np.complex128
|
|
56
|
+
)
|
|
57
|
+
atol: float = 1e-12
|
|
58
|
+
|
|
59
|
+
def __call__(
|
|
60
|
+
self,
|
|
61
|
+
epi: float,
|
|
62
|
+
nu_f: float,
|
|
63
|
+
theta: float,
|
|
64
|
+
dim: int,
|
|
65
|
+
rng: np.random.Generator | None = None,
|
|
66
|
+
) -> ComplexVector:
|
|
67
|
+
if dim <= 0:
|
|
68
|
+
raise ValueError("State dimension must be a positive integer.")
|
|
69
|
+
|
|
70
|
+
indices = np.arange(1, dim + 1, dtype=float)
|
|
71
|
+
phase_progression = theta + (nu_f + 1.0) * indices / max(dim, 1)
|
|
72
|
+
envelope = np.abs(epi) + 0.5 * indices / dim + 1.0
|
|
73
|
+
base_vector = envelope * np.exp(1j * phase_progression)
|
|
74
|
+
|
|
75
|
+
if rng is not None:
|
|
76
|
+
noise_scale = (np.abs(epi) + np.abs(nu_f) + 1.0) * 0.05
|
|
77
|
+
real_noise = rng.standard_normal(dim)
|
|
78
|
+
imag_noise = rng.standard_normal(dim)
|
|
79
|
+
stochastic = noise_scale * (real_noise + 1j * imag_noise)
|
|
80
|
+
base_vector = base_vector + stochastic
|
|
81
|
+
|
|
82
|
+
norm = np.linalg.norm(base_vector)
|
|
83
|
+
if np.isclose(norm, 0.0, atol=self.atol):
|
|
84
|
+
raise ValueError("Cannot normalise a null state vector.")
|
|
85
|
+
|
|
86
|
+
normalised = base_vector / norm
|
|
87
|
+
return np.asarray(normalised, dtype=self.dtype)
|