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,453 @@
1
+ """Backend abstraction for TNFR mathematical kernels.
2
+
3
+ This module introduces a unified interface that maps core linear algebra
4
+ operations to concrete numerical libraries. Keeping this layer small and
5
+ canonical guarantees we can switch implementations without diluting the
6
+ structural semantics required by TNFR (coherence, phase, νf, ΔNFR, etc.).
7
+
8
+ The canonical entry point is :func:`get_backend`, which honours three lookup
9
+ mechanisms in order of precedence:
10
+
11
+ 1. Explicit ``name`` argument.
12
+ 2. ``TNFR_MATH_BACKEND`` environment variable.
13
+ 3. ``tnfr.config.get_flags().math_backend``.
14
+
15
+ If none of these provide a value we default to the NumPy backend. Optional
16
+ backends are registered lazily so downstream environments without JAX or
17
+ PyTorch remain functional.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from ..compat.dataclass import dataclass
23
+ import os
24
+ from typing import (
25
+ Any,
26
+ Callable,
27
+ ClassVar,
28
+ Iterable,
29
+ Mapping,
30
+ MutableMapping,
31
+ Protocol,
32
+ runtime_checkable,
33
+ )
34
+
35
+ from ..utils import cached_import, get_logger
36
+
37
+ logger = get_logger(__name__)
38
+
39
+
40
+ class BackendUnavailableError(RuntimeError):
41
+ """Raised when a registered backend cannot be constructed."""
42
+
43
+
44
+ @runtime_checkable
45
+ class MathematicsBackend(Protocol):
46
+ """Structural numerical backend interface.
47
+
48
+ Notes
49
+ -----
50
+ Marked with @runtime_checkable to enable isinstance() checks for validating
51
+ backend implementations conform to the expected mathematical operations interface.
52
+ """
53
+
54
+ name: str
55
+ supports_autodiff: bool
56
+
57
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any:
58
+ """Convert ``value`` into a backend-native dense array."""
59
+
60
+ def eig(self, matrix: Any) -> tuple[Any, Any]:
61
+ """Return eigenvalues and eigenvectors for a general matrix."""
62
+
63
+ def eigh(self, matrix: Any) -> tuple[Any, Any]:
64
+ """Return eigenpairs for a Hermitian/symmetric matrix."""
65
+
66
+ def matrix_exp(self, matrix: Any) -> Any:
67
+ """Compute the matrix exponential of ``matrix``."""
68
+
69
+ def norm(
70
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
71
+ ) -> Any:
72
+ """Return the matrix or vector norm according to ``ord``."""
73
+
74
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any:
75
+ """Evaluate an Einstein summation expression."""
76
+
77
+ def matmul(self, a: Any, b: Any) -> Any:
78
+ """Matrix multiplication that respects backend broadcasting rules."""
79
+
80
+ def conjugate_transpose(self, matrix: Any) -> Any:
81
+ """Hermitian conjugate of ``matrix`` († operator)."""
82
+
83
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any:
84
+ """Stack arrays along a new ``axis``."""
85
+
86
+ def to_numpy(self, value: Any) -> Any:
87
+ """Convert ``value`` to a ``numpy.ndarray`` when possible."""
88
+
89
+
90
+ BackendFactory = Callable[[], MathematicsBackend]
91
+
92
+
93
+ @dataclass(slots=True)
94
+ class _NumpyBackend:
95
+ """NumPy backed implementation."""
96
+
97
+ _np: Any
98
+ _scipy_linalg: Any | None
99
+
100
+ name: ClassVar[str] = "numpy"
101
+ supports_autodiff: ClassVar[bool] = False
102
+
103
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any:
104
+ return self._np.asarray(value, dtype=dtype)
105
+
106
+ def eig(self, matrix: Any) -> tuple[Any, Any]:
107
+ return self._np.linalg.eig(matrix)
108
+
109
+ def eigh(self, matrix: Any) -> tuple[Any, Any]:
110
+ return self._np.linalg.eigh(matrix)
111
+
112
+ def matrix_exp(self, matrix: Any) -> Any:
113
+ if self._scipy_linalg is not None:
114
+ return self._scipy_linalg.expm(matrix)
115
+ eigvals, eigvecs = self._np.linalg.eig(matrix)
116
+ inv = self._np.linalg.inv(eigvecs)
117
+ exp_vals = self._np.exp(eigvals)
118
+ return eigvecs @ self._np.diag(exp_vals) @ inv
119
+
120
+ def norm(
121
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
122
+ ) -> Any:
123
+ return self._np.linalg.norm(value, ord=ord, axis=axis)
124
+
125
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any:
126
+ return self._np.einsum(pattern, *operands, **kwargs)
127
+
128
+ def matmul(self, a: Any, b: Any) -> Any:
129
+ return self._np.matmul(a, b)
130
+
131
+ def conjugate_transpose(self, matrix: Any) -> Any:
132
+ return self._np.conjugate(matrix).T
133
+
134
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any:
135
+ return self._np.stack(tuple(arrays), axis=axis)
136
+
137
+ def to_numpy(self, value: Any) -> Any:
138
+ return self._np.asarray(value)
139
+
140
+
141
+ @dataclass(slots=True)
142
+ class _JaxBackend:
143
+ """JAX backed implementation."""
144
+
145
+ _jnp: Any
146
+ _jax_linalg: Any
147
+ _jax: Any
148
+
149
+ name: ClassVar[str] = "jax"
150
+ supports_autodiff: ClassVar[bool] = True
151
+
152
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any:
153
+ return self._jnp.asarray(value, dtype=dtype)
154
+
155
+ def eig(self, matrix: Any) -> tuple[Any, Any]:
156
+ return self._jnp.linalg.eig(matrix)
157
+
158
+ def eigh(self, matrix: Any) -> tuple[Any, Any]:
159
+ return self._jnp.linalg.eigh(matrix)
160
+
161
+ def matrix_exp(self, matrix: Any) -> Any:
162
+ return self._jax_linalg.expm(matrix)
163
+
164
+ def norm(
165
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
166
+ ) -> Any:
167
+ return self._jnp.linalg.norm(value, ord=ord, axis=axis)
168
+
169
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any:
170
+ return self._jnp.einsum(pattern, *operands, **kwargs)
171
+
172
+ def matmul(self, a: Any, b: Any) -> Any:
173
+ return self._jnp.matmul(a, b)
174
+
175
+ def conjugate_transpose(self, matrix: Any) -> Any:
176
+ return self._jnp.conjugate(matrix).T
177
+
178
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any:
179
+ return self._jnp.stack(tuple(arrays), axis=axis)
180
+
181
+ def to_numpy(self, value: Any) -> Any:
182
+ np_mod = cached_import("numpy")
183
+ if np_mod is None:
184
+ raise BackendUnavailableError("NumPy is required to export JAX arrays")
185
+ return np_mod.asarray(self._jax.device_get(value))
186
+
187
+
188
+ @dataclass(slots=True)
189
+ class _TorchBackend:
190
+ """PyTorch backed implementation."""
191
+
192
+ _torch: Any
193
+ _torch_linalg: Any
194
+
195
+ name: ClassVar[str] = "torch"
196
+ supports_autodiff: ClassVar[bool] = True
197
+
198
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any:
199
+ tensor = self._torch.as_tensor(value)
200
+ if dtype is None:
201
+ return tensor
202
+
203
+ target_dtype = self._normalise_dtype(dtype)
204
+ if target_dtype is None:
205
+ return tensor.to(dtype=dtype)
206
+
207
+ if tensor.dtype == target_dtype:
208
+ return tensor
209
+
210
+ return tensor.to(dtype=target_dtype)
211
+
212
+ def _normalise_dtype(self, dtype: Any) -> Any | None:
213
+ """Return a ``torch.dtype`` equivalent for ``dtype`` when available."""
214
+
215
+ if isinstance(dtype, self._torch.dtype):
216
+ return dtype
217
+
218
+ np_mod = cached_import("numpy")
219
+ if np_mod is None:
220
+ return None
221
+
222
+ try:
223
+ np_dtype = np_mod.dtype(dtype)
224
+ except TypeError:
225
+ return None
226
+
227
+ numpy_name = np_dtype.name
228
+ numpy_to_torch = {
229
+ "bool": self._torch.bool,
230
+ "uint8": self._torch.uint8,
231
+ "int8": self._torch.int8,
232
+ "int16": self._torch.int16,
233
+ "int32": self._torch.int32,
234
+ "int64": self._torch.int64,
235
+ "float16": self._torch.float16,
236
+ "float32": self._torch.float32,
237
+ "float64": self._torch.float64,
238
+ "complex64": getattr(self._torch, "complex64", None),
239
+ "complex128": getattr(self._torch, "complex128", None),
240
+ "bfloat16": getattr(self._torch, "bfloat16", None),
241
+ }
242
+
243
+ torch_dtype = numpy_to_torch.get(numpy_name)
244
+ return torch_dtype
245
+
246
+ def eig(self, matrix: Any) -> tuple[Any, Any]:
247
+ eigenvalues, eigenvectors = self._torch.linalg.eig(matrix)
248
+ return eigenvalues, eigenvectors
249
+
250
+ def eigh(self, matrix: Any) -> tuple[Any, Any]:
251
+ eigenvalues, eigenvectors = self._torch.linalg.eigh(matrix)
252
+ return eigenvalues, eigenvectors
253
+
254
+ def matrix_exp(self, matrix: Any) -> Any:
255
+ return self._torch_linalg.matrix_exp(matrix)
256
+
257
+ def norm(
258
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
259
+ ) -> Any:
260
+ if axis is None:
261
+ return self._torch.linalg.norm(value, ord=ord)
262
+ return self._torch.linalg.norm(value, ord=ord, dim=axis)
263
+
264
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any:
265
+ return self._torch.einsum(pattern, *operands, **kwargs)
266
+
267
+ def matmul(self, a: Any, b: Any) -> Any:
268
+ return self._torch.matmul(a, b)
269
+
270
+ def conjugate_transpose(self, matrix: Any) -> Any:
271
+ return matrix.mH if hasattr(matrix, "mH") else matrix.conj().transpose(-2, -1)
272
+
273
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any:
274
+ return self._torch.stack(tuple(arrays), dim=axis)
275
+
276
+ def to_numpy(self, value: Any) -> Any:
277
+ np_mod = cached_import("numpy")
278
+ if np_mod is None:
279
+ raise BackendUnavailableError("NumPy is required to export Torch tensors")
280
+ if hasattr(value, "detach"):
281
+ return value.detach().cpu().numpy()
282
+ return np_mod.asarray(value)
283
+
284
+
285
+ def _normalise_name(name: str) -> str:
286
+ return name.strip().lower()
287
+
288
+
289
+ _BACKEND_FACTORIES: MutableMapping[str, BackendFactory] = {}
290
+ _BACKEND_ALIASES: MutableMapping[str, str] = {}
291
+ _BACKEND_CACHE: MutableMapping[str, MathematicsBackend] = {}
292
+
293
+
294
+ def ensure_array(
295
+ value: Any,
296
+ *,
297
+ dtype: Any | None = None,
298
+ backend: MathematicsBackend | None = None,
299
+ ) -> Any:
300
+ """Return ``value`` as a backend-native dense array."""
301
+
302
+ resolved = backend or get_backend()
303
+ return resolved.as_array(value, dtype=dtype)
304
+
305
+
306
+ def ensure_numpy(value: Any, *, backend: MathematicsBackend | None = None) -> Any:
307
+ """Export ``value`` from the backend into :class:`numpy.ndarray`."""
308
+
309
+ resolved = backend or get_backend()
310
+ return resolved.to_numpy(value)
311
+
312
+
313
+ def register_backend(
314
+ name: str,
315
+ factory: BackendFactory,
316
+ *,
317
+ aliases: Iterable[str] | None = None,
318
+ override: bool = False,
319
+ ) -> None:
320
+ """Register a backend factory under ``name``.
321
+
322
+ Parameters
323
+ ----------
324
+ name:
325
+ Canonical backend identifier.
326
+ factory:
327
+ Callable that returns a :class:`MathematicsBackend` instance.
328
+ aliases:
329
+ Optional alternative identifiers that will resolve to ``name``.
330
+ override:
331
+ When ``True`` replaces existing registrations.
332
+ """
333
+
334
+ key = _normalise_name(name)
335
+ if not override and key in _BACKEND_FACTORIES:
336
+ raise ValueError(f"Backend '{name}' already registered")
337
+ _BACKEND_FACTORIES[key] = factory
338
+ if aliases:
339
+ for alias in aliases:
340
+ alias_key = _normalise_name(alias)
341
+ if not override and alias_key in _BACKEND_ALIASES:
342
+ raise ValueError(f"Backend alias '{alias}' already registered")
343
+ _BACKEND_ALIASES[alias_key] = key
344
+
345
+
346
+ def _resolve_backend_name(name: str | None) -> str:
347
+ if name:
348
+ return _normalise_name(name)
349
+
350
+ env_choice = os.getenv("TNFR_MATH_BACKEND")
351
+ if env_choice:
352
+ return _normalise_name(env_choice)
353
+
354
+ backend_from_flags: str | None = None
355
+ try:
356
+ from ..config import get_flags # Local import avoids circular dependency
357
+
358
+ backend_from_flags = getattr(get_flags(), "math_backend", None)
359
+ except Exception: # pragma: no cover - defensive; config must not break selection
360
+ backend_from_flags = None
361
+
362
+ if backend_from_flags:
363
+ return _normalise_name(backend_from_flags)
364
+
365
+ return "numpy"
366
+
367
+
368
+ def _resolve_factory(name: str) -> BackendFactory:
369
+ canonical = _BACKEND_ALIASES.get(name, name)
370
+ try:
371
+ return _BACKEND_FACTORIES[canonical]
372
+ except KeyError as exc: # pragma: no cover - defensive path
373
+ raise LookupError(f"Unknown mathematics backend: {name}") from exc
374
+
375
+
376
+ def get_backend(name: str | None = None) -> MathematicsBackend:
377
+ """Return a backend instance using the configured resolution order."""
378
+
379
+ resolved_name = _resolve_backend_name(name)
380
+ canonical = _BACKEND_ALIASES.get(resolved_name, resolved_name)
381
+ if canonical in _BACKEND_CACHE:
382
+ return _BACKEND_CACHE[canonical]
383
+
384
+ factory = _resolve_factory(canonical)
385
+ try:
386
+ backend = factory()
387
+ except BackendUnavailableError as exc:
388
+ logger.warning("Backend '%s' unavailable: %s", canonical, exc)
389
+ if canonical != "numpy":
390
+ logger.warning("Falling back to NumPy backend")
391
+ return get_backend("numpy")
392
+ raise
393
+
394
+ _BACKEND_CACHE[canonical] = backend
395
+ return backend
396
+
397
+
398
+ def available_backends() -> Mapping[str, BackendFactory]:
399
+ """Return the registered backend factories."""
400
+
401
+ return dict(_BACKEND_FACTORIES)
402
+
403
+
404
+ def _make_numpy_backend() -> MathematicsBackend:
405
+ np_module = cached_import("numpy")
406
+ if np_module is None:
407
+ raise BackendUnavailableError("NumPy is not installed")
408
+ scipy_linalg = cached_import("scipy.linalg")
409
+ if scipy_linalg is None:
410
+ logger.debug(
411
+ "SciPy not available; falling back to eigen decomposition for expm"
412
+ )
413
+ return _NumpyBackend(np_module, scipy_linalg)
414
+
415
+
416
+ def _make_jax_backend() -> MathematicsBackend:
417
+ jnp_module = cached_import("jax.numpy")
418
+ if jnp_module is None:
419
+ raise BackendUnavailableError("jax.numpy is not available")
420
+ jax_scipy = cached_import("jax.scipy.linalg")
421
+ if jax_scipy is None:
422
+ raise BackendUnavailableError("jax.scipy.linalg is required for matrix_exp")
423
+ jax_module = cached_import("jax")
424
+ if jax_module is None:
425
+ raise BackendUnavailableError("jax core module is required")
426
+ return _JaxBackend(jnp_module, jax_scipy, jax_module)
427
+
428
+
429
+ def _make_torch_backend() -> MathematicsBackend:
430
+ torch_module = cached_import("torch")
431
+ if torch_module is None:
432
+ raise BackendUnavailableError("PyTorch is not installed")
433
+ torch_linalg = cached_import("torch.linalg")
434
+ if torch_linalg is None:
435
+ raise BackendUnavailableError(
436
+ "torch.linalg is required for linear algebra operations"
437
+ )
438
+ return _TorchBackend(torch_module, torch_linalg)
439
+
440
+
441
+ register_backend("numpy", _make_numpy_backend, aliases=("np",))
442
+ register_backend("jax", _make_jax_backend)
443
+ register_backend("torch", _make_torch_backend, aliases=("pytorch",))
444
+
445
+ __all__ = [
446
+ "MathematicsBackend",
447
+ "BackendUnavailableError",
448
+ "register_backend",
449
+ "get_backend",
450
+ "available_backends",
451
+ "ensure_array",
452
+ "ensure_numpy",
453
+ ]
@@ -0,0 +1,99 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable, ClassVar, Iterable, Mapping, Protocol
5
+
6
+ __all__ = [
7
+ "MathematicsBackend",
8
+ "BackendUnavailableError",
9
+ "register_backend",
10
+ "get_backend",
11
+ "available_backends",
12
+ "ensure_array",
13
+ "ensure_numpy",
14
+ ]
15
+
16
+ class BackendUnavailableError(RuntimeError): ...
17
+
18
+ class MathematicsBackend(Protocol):
19
+ name: str
20
+ supports_autodiff: bool
21
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any: ...
22
+ def eig(self, matrix: Any) -> tuple[Any, Any]: ...
23
+ def eigh(self, matrix: Any) -> tuple[Any, Any]: ...
24
+ def matrix_exp(self, matrix: Any) -> Any: ...
25
+ def norm(
26
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
27
+ ) -> Any: ...
28
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any: ...
29
+ def matmul(self, a: Any, b: Any) -> Any: ...
30
+ def conjugate_transpose(self, matrix: Any) -> Any: ...
31
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any: ...
32
+ def to_numpy(self, value: Any) -> Any: ...
33
+
34
+ BackendFactory = Callable[[], MathematicsBackend]
35
+
36
+ @dataclass
37
+ class _NumpyBackend:
38
+ name: ClassVar[str] = ...
39
+ supports_autodiff: ClassVar[bool] = ...
40
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any: ...
41
+ def eig(self, matrix: Any) -> tuple[Any, Any]: ...
42
+ def eigh(self, matrix: Any) -> tuple[Any, Any]: ...
43
+ def matrix_exp(self, matrix: Any) -> Any: ...
44
+ def norm(
45
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
46
+ ) -> Any: ...
47
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any: ...
48
+ def matmul(self, a: Any, b: Any) -> Any: ...
49
+ def conjugate_transpose(self, matrix: Any) -> Any: ...
50
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any: ...
51
+ def to_numpy(self, value: Any) -> Any: ...
52
+
53
+ @dataclass
54
+ class _JaxBackend:
55
+ name: ClassVar[str] = ...
56
+ supports_autodiff: ClassVar[bool] = ...
57
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any: ...
58
+ def eig(self, matrix: Any) -> tuple[Any, Any]: ...
59
+ def eigh(self, matrix: Any) -> tuple[Any, Any]: ...
60
+ def matrix_exp(self, matrix: Any) -> Any: ...
61
+ def norm(
62
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
63
+ ) -> Any: ...
64
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any: ...
65
+ def matmul(self, a: Any, b: Any) -> Any: ...
66
+ def conjugate_transpose(self, matrix: Any) -> Any: ...
67
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any: ...
68
+ def to_numpy(self, value: Any) -> Any: ...
69
+
70
+ @dataclass
71
+ class _TorchBackend:
72
+ name: ClassVar[str] = ...
73
+ supports_autodiff: ClassVar[bool] = ...
74
+ def as_array(self, value: Any, *, dtype: Any | None = None) -> Any: ...
75
+ def eig(self, matrix: Any) -> tuple[Any, Any]: ...
76
+ def eigh(self, matrix: Any) -> tuple[Any, Any]: ...
77
+ def matrix_exp(self, matrix: Any) -> Any: ...
78
+ def norm(
79
+ self, value: Any, *, ord: Any | None = None, axis: Any | None = None
80
+ ) -> Any: ...
81
+ def einsum(self, pattern: str, *operands: Any, **kwargs: Any) -> Any: ...
82
+ def matmul(self, a: Any, b: Any) -> Any: ...
83
+ def conjugate_transpose(self, matrix: Any) -> Any: ...
84
+ def stack(self, arrays: Iterable[Any], *, axis: int = 0) -> Any: ...
85
+ def to_numpy(self, value: Any) -> Any: ...
86
+
87
+ def ensure_array(
88
+ value: Any, *, dtype: Any | None = None, backend: MathematicsBackend | None = None
89
+ ) -> Any: ...
90
+ def ensure_numpy(value: Any, *, backend: MathematicsBackend | None = None) -> Any: ...
91
+ def register_backend(
92
+ name: str,
93
+ factory: BackendFactory,
94
+ *,
95
+ aliases: Iterable[str] | None = None,
96
+ override: bool = False,
97
+ ) -> None: ...
98
+ def get_backend(name: str | None = None) -> MathematicsBackend: ...
99
+ def available_backends() -> Mapping[str, BackendFactory]: ...