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.

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