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
tnfr/alias.py CHANGED
@@ -4,39 +4,113 @@
4
4
  alias-based attribute access. Legacy wrappers ``alias_get`` and
5
5
  ``alias_set`` have been removed; use :func:`get_attr` and
6
6
  :func:`set_attr` instead.
7
+
8
+ CRITICAL: Canonical Attribute Access
9
+ =====================================
10
+
11
+ **ALWAYS use the alias system for reading/writing TNFR attributes.**
12
+
13
+ The TNFR canonical attribute keys use Unicode symbols (e.g., 'νf' for structural
14
+ frequency), but NetworkX and Python code often use ASCII equivalents (e.g., 'vf').
15
+ This creates a critical inconsistency:
16
+
17
+ **WRONG (breaks canonicity)**:
18
+ >>> G.add_node(0, vf=1.0) # Uses ASCII 'vf' key
19
+ >>> value = G.nodes[0]['vf'] # Reads ASCII 'vf' - may not exist!
20
+ >>> G.nodes[0]['vf'] = 2.0 # Writes ASCII 'vf' - wrong key!
21
+
22
+ **CORRECT (maintains canonicity)**:
23
+ >>> from tnfr.alias import set_vf, get_attr
24
+ >>> from tnfr.constants.aliases import ALIAS_VF
25
+ >>> from tnfr.constants import VF_PRIMARY
26
+ >>>
27
+ >>> # For initialization, use canonical setters:
28
+ >>> set_vf(G, 0, 1.0) # Writes to 'νf' (Greek nu)
29
+ >>>
30
+ >>> # For reading, use canonical getters:
31
+ >>> value = get_attr(G.nodes[0], ALIAS_VF, 0.0) # Reads from 'νf'
32
+ >>>
33
+ >>> # Or use PRIMARY constants in add_node:
34
+ >>> G.add_node(1, **{VF_PRIMARY: 1.0}) # Writes to 'νf' directly
35
+
36
+ **Why This Matters**:
37
+ - The alias system tries ALL aliases in order: ('νf', 'nu_f', 'nu-f', 'nu', 'freq', 'frequency')
38
+ - If you write to 'vf', the data is stored under a key NOT in the alias list
39
+ - Reading via get_attr() will return the default (0.0) instead of your value
40
+ - This breaks the nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
41
+
42
+ **For Tests**:
43
+ >>> from tnfr.structural import create_nfr
44
+ >>> # PREFERRED: Use create_nfr which handles canonicity
45
+ >>> G, node = create_nfr("test", vf=1.0, epi=0.5, theta=0.0)
46
+ >>>
47
+ >>> # ALTERNATIVE: Manual initialization with canonical setters
48
+ >>> from tnfr.alias import set_vf, get_attr
49
+ >>> from tnfr.constants.aliases import ALIAS_VF
50
+ >>> G = nx.Graph()
51
+ >>> G.add_node(0, theta=0.0, EPI=1.0, Si=0.5) # Other attrs OK
52
+ >>> set_vf(G, 0, 1.0) # Use canonical setter for vf
53
+ >>> value = get_attr(G.nodes[0], ALIAS_VF, 0.0) # Use canonical getter
54
+
55
+ **Applies to**: νf (vf), θ (theta), ΔNFR (dnfr), and other aliased attributes.
56
+ See ALIAS_VF, ALIAS_THETA, ALIAS_DNFR in tnfr.constants.aliases for full lists.
7
57
  """
8
58
 
9
59
  from __future__ import annotations
60
+
10
61
  from collections import defaultdict
11
- from collections.abc import Iterable, Sized
12
- from dataclasses import dataclass
62
+ from collections.abc import Iterable, Mapping, MutableMapping, Sized
63
+ from functools import lru_cache, partial
64
+ from threading import Lock
65
+ from types import ModuleType
13
66
  from typing import (
67
+ TYPE_CHECKING,
14
68
  Any,
15
69
  Callable,
16
- TypeVar,
17
- Optional,
18
70
  Generic,
19
71
  Hashable,
20
- TYPE_CHECKING,
72
+ Optional,
73
+ TypeVar,
21
74
  cast,
22
75
  )
23
76
 
24
- from functools import lru_cache, partial
25
- from threading import Lock
26
-
27
- from .constants import get_aliases
28
- from .value_utils import convert_value
29
-
30
- ALIAS_VF = get_aliases("VF")
31
- ALIAS_DNFR = get_aliases("DNFR")
32
- ALIAS_THETA = get_aliases("THETA")
77
+ from .compat.dataclass import dataclass
78
+ from .constants.aliases import ALIAS_DNFR, ALIAS_THETA, ALIAS_VF
79
+ from .types import FloatArray, NodeId
80
+ from .utils import convert_value
33
81
 
34
82
  if TYPE_CHECKING: # pragma: no cover
35
- import networkx # type: ignore[import-untyped]
83
+ import networkx
36
84
 
37
85
  T = TypeVar("T")
38
86
 
39
87
 
88
+ def _bepi_to_float(value: Any) -> float:
89
+ """Extract scalar from BEPIElement dict or convert value to float.
90
+
91
+ When operators transform EPI from float to BEPIElement dict, this helper
92
+ extracts the maximum magnitude from the 'continuous' component. This
93
+ preserves ΔNFR semantics (§3.3) and structural metrics accuracy (§3.9).
94
+
95
+ Parameters
96
+ ----------
97
+ value : Any
98
+ Value to convert. If it's a dict with a 'continuous' key, extracts
99
+ the maximum magnitude. Otherwise converts directly to float.
100
+
101
+ Returns
102
+ -------
103
+ float
104
+ Scalar representation of the value.
105
+ """
106
+ if isinstance(value, dict) and "continuous" in value:
107
+ cont = value["continuous"]
108
+ if isinstance(cont, tuple):
109
+ return float(max(abs(c) for c in cont)) if cont else 0.0
110
+ return float(abs(cont))
111
+ return float(value)
112
+
113
+
40
114
  @lru_cache(maxsize=128)
41
115
  def _alias_cache(alias_tuple: tuple[str, ...]) -> tuple[str, ...]:
42
116
  """Validate and cache alias tuples.
@@ -132,6 +206,8 @@ class AliasAccessor(Generic[T]):
132
206
  log_level: int | None = None,
133
207
  conv: Callable[[Any], T] | None = None,
134
208
  ) -> Optional[T]:
209
+ """Return ``value`` for the first alias present in ``d``."""
210
+
135
211
  aliases, conv, default = self._prepare(aliases, conv, default)
136
212
  cache_key, key = self._resolve_cache_key(d, aliases)
137
213
  if key is not None:
@@ -168,6 +244,8 @@ class AliasAccessor(Generic[T]):
168
244
  value: Any,
169
245
  conv: Callable[[Any], T] | None = None,
170
246
  ) -> T:
247
+ """Write ``value`` under the first matching alias and cache the choice."""
248
+
171
249
  aliases, conv, _ = self._prepare(aliases, conv)
172
250
  cache_key, key = self._resolve_cache_key(d, aliases)
173
251
  if key is not None:
@@ -184,6 +262,25 @@ class AliasAccessor(Generic[T]):
184
262
  _generic_accessor: AliasAccessor[Any] = AliasAccessor()
185
263
 
186
264
 
265
+ def get_theta_attr(
266
+ d: Mapping[str, Any],
267
+ default: T | None = None,
268
+ *,
269
+ strict: bool = False,
270
+ log_level: int | None = None,
271
+ conv: Callable[[Any], T] = float,
272
+ ) -> T | None:
273
+ """Return ``theta``/``phase`` using the English alias set."""
274
+ return _generic_accessor.get(
275
+ cast(dict[str, Any], d),
276
+ ALIAS_THETA,
277
+ default,
278
+ strict=strict,
279
+ log_level=log_level,
280
+ conv=conv,
281
+ )
282
+
283
+
187
284
  def get_attr(
188
285
  d: dict[str, Any],
189
286
  aliases: Iterable[str],
@@ -191,9 +288,17 @@ def get_attr(
191
288
  *,
192
289
  strict: bool = False,
193
290
  log_level: int | None = None,
194
- conv: Callable[[Any], T] = float,
291
+ conv: Callable[[Any], T] = _bepi_to_float,
195
292
  ) -> T | None:
196
- """Return the value for the first key in ``aliases`` found in ``d``."""
293
+ """Return the value for the first key in ``aliases`` found in ``d``.
294
+
295
+ WARNING: This function searches for keys in alias order. If you manually
296
+ wrote to a non-canonical key (e.g., 'vf' instead of 'νf'), this function
297
+ will NOT find it and will return the default value instead.
298
+
299
+ For structural frequency: ALWAYS use set_vf() to write, not d['vf'] = value.
300
+ See module docstring for detailed guidance on canonical attribute access.
301
+ """
197
302
 
198
303
  return _generic_accessor.get(
199
304
  d,
@@ -207,12 +312,12 @@ def get_attr(
207
312
 
208
313
  def collect_attr(
209
314
  G: "networkx.Graph",
210
- nodes: Iterable[Any],
315
+ nodes: Iterable[NodeId],
211
316
  aliases: Iterable[str],
212
317
  default: float = 0.0,
213
318
  *,
214
- np=None,
215
- ):
319
+ np: ModuleType | None = None,
320
+ ) -> FloatArray | list[float]:
216
321
  """Collect attribute values for ``nodes`` from ``G`` using ``aliases``.
217
322
 
218
323
  Parameters
@@ -235,7 +340,7 @@ def collect_attr(
235
340
  Collected attribute values in the same order as ``nodes``.
236
341
  """
237
342
 
238
- def _nodes_iter_and_size(nodes: Iterable[Any]) -> tuple[Iterable[Any], int]:
343
+ def _nodes_iter_and_size(nodes: Iterable[NodeId]) -> tuple[Iterable[NodeId], int]:
239
344
  if nodes is G.nodes:
240
345
  return G.nodes, G.number_of_nodes()
241
346
  if isinstance(nodes, Sized):
@@ -245,13 +350,46 @@ def collect_attr(
245
350
 
246
351
  nodes_iter, size = _nodes_iter_and_size(nodes)
247
352
 
353
+ def _value(node: NodeId) -> float:
354
+ return float(get_attr(G.nodes[node], aliases, default))
355
+
248
356
  if np is not None:
249
- return np.fromiter(
250
- (get_attr(G.nodes[n], aliases, default) for n in nodes_iter),
251
- float,
252
- count=size,
357
+ values: FloatArray = np.fromiter(
358
+ (_value(n) for n in nodes_iter), float, count=size
253
359
  )
254
- return [get_attr(G.nodes[n], aliases, default) for n in nodes_iter]
360
+ return values
361
+ return [_value(n) for n in nodes_iter]
362
+
363
+
364
+ def collect_theta_attr(
365
+ G: "networkx.Graph",
366
+ nodes: Iterable[NodeId],
367
+ default: float = 0.0,
368
+ *,
369
+ np: ModuleType | None = None,
370
+ ) -> FloatArray | list[float]:
371
+ """Collect ``theta`` values honouring the English-only attribute contract."""
372
+
373
+ def _nodes_iter_and_size(nodes: Iterable[NodeId]) -> tuple[Iterable[NodeId], int]:
374
+ if nodes is G.nodes:
375
+ return G.nodes, G.number_of_nodes()
376
+ if isinstance(nodes, Sized):
377
+ return nodes, len(nodes) # type: ignore[arg-type]
378
+ nodes_list = list(nodes)
379
+ return nodes_list, len(nodes_list)
380
+
381
+ nodes_iter, size = _nodes_iter_and_size(nodes)
382
+
383
+ def _value(node: NodeId) -> float:
384
+ return float(get_theta_attr(G.nodes[node], default))
385
+
386
+ if np is not None:
387
+ values: FloatArray = np.fromiter(
388
+ (_value(n) for n in nodes_iter), float, count=size
389
+ )
390
+ return values
391
+
392
+ return [_value(n) for n in nodes_iter]
255
393
 
256
394
 
257
395
  def set_attr_generic(
@@ -261,20 +399,37 @@ def set_attr_generic(
261
399
  *,
262
400
  conv: Callable[[Any], T],
263
401
  ) -> T:
264
- """Assign ``value`` to the first alias key found in ``d``."""
402
+ """Assign ``value`` to the FIRST (canonical) alias key in ``aliases``.
403
+
404
+ CRITICAL: This function writes to the FIRST key in the alias tuple.
405
+ For ALIAS_VF = ('νf', 'nu_f', ...), this writes to 'νf' (Greek nu), NOT 'vf'.
406
+
407
+ If you later try to read with G.nodes[n]['vf'], you will NOT find the value.
408
+ ALWAYS use get_attr() to read what set_attr() wrote.
409
+
410
+ For high-level usage, prefer set_vf(), set_theta(), etc. which handle this correctly.
411
+ See module docstring for detailed guidance on canonical attribute access.
412
+ """
265
413
 
266
414
  return _generic_accessor.set(d, aliases, value, conv=conv)
267
415
 
268
416
 
269
417
  set_attr = partial(set_attr_generic, conv=float)
270
418
 
271
-
272
419
  get_attr_str = partial(get_attr, conv=str)
273
420
  set_attr_str = partial(set_attr_generic, conv=str)
274
421
 
275
422
 
423
+ def set_theta_attr(d: MutableMapping[str, Any], value: Any) -> float:
424
+ """Assign ``theta``/``phase`` using the English attribute names."""
425
+ result = float(value)
426
+ d["theta"] = result
427
+ d["phase"] = result
428
+ return result
429
+
430
+
276
431
  # -------------------------
277
- # Máximos globales con caché
432
+ # Cached global maxima
278
433
  # -------------------------
279
434
 
280
435
 
@@ -292,7 +447,7 @@ def _coerce_abs_value(value: Any) -> float:
292
447
  if value is None:
293
448
  return 0.0
294
449
  try:
295
- return float(value)
450
+ return _bepi_to_float(value)
296
451
  except (TypeError, ValueError):
297
452
  return 0.0
298
453
 
@@ -388,9 +543,7 @@ def _update_cached_abs_max(
388
543
  cur_node = cast(Hashable | None, G.graph.get(node_key))
389
544
 
390
545
  if val >= cur:
391
- return _compute_abs_max_result(
392
- G, aliases, key=key, candidate=(n, val)
393
- )
546
+ return _compute_abs_max_result(G, aliases, key=key, candidate=(n, val))
394
547
  if cur_node == n:
395
548
  return _compute_abs_max_result(G, aliases, key=key)
396
549
  return AbsMaxResult(max_value=cur, node=cur_node)
@@ -461,9 +614,7 @@ def set_scalar(
461
614
  return set_attr_and_cache(G, n, alias, value, cache=cache, extra=extra)
462
615
 
463
616
 
464
- def _increment_trig_version(
465
- G: "networkx.Graph", _: Hashable, __: float
466
- ) -> None:
617
+ def _increment_trig_version(G: "networkx.Graph", _: Hashable, __: float) -> None:
467
618
  """Increment cached trig version to invalidate trig caches."""
468
619
  g = G.graph
469
620
  g["_trig_version"] = int(g.get("_trig_version", 0)) + 1
@@ -484,7 +635,7 @@ SCALAR_SETTERS: dict[str, dict[str, Any]] = {
484
635
  "theta": {
485
636
  "alias": ALIAS_THETA,
486
637
  "extra": _increment_trig_version,
487
- "doc": "Set ``θ`` for node ``n`` and invalidate trig caches.",
638
+ "doc": "Set ``theta`` for node ``n`` and invalidate trig caches.",
488
639
  },
489
640
  }
490
641
 
@@ -528,15 +679,41 @@ for _name, _spec in SCALAR_SETTERS.items():
528
679
 
529
680
  del _name, _spec, _make_scalar_setter
530
681
 
682
+ _set_theta_impl = cast(
683
+ Callable[["networkx.Graph", Hashable, float], AbsMaxResult | None],
684
+ globals()["set_theta"],
685
+ )
686
+
687
+
688
+ def _set_theta_with_compat(
689
+ G: "networkx.Graph", n: Hashable, value: float
690
+ ) -> AbsMaxResult | None:
691
+ nd = cast(MutableMapping[str, Any], G.nodes[n])
692
+ result = _set_theta_impl(G, n, value)
693
+ theta_val = get_theta_attr(nd, value)
694
+ if theta_val is not None:
695
+ float_theta = float(theta_val)
696
+ nd["theta"] = float_theta
697
+ nd["phase"] = float_theta
698
+ return result
699
+
700
+
701
+ _set_theta_with_compat.__name__ = "set_theta"
702
+ _set_theta_with_compat.__qualname__ = "set_theta"
703
+ _set_theta_with_compat.__doc__ = _set_theta_impl.__doc__
704
+ globals()["set_theta"] = _set_theta_with_compat
531
705
 
532
706
  __all__ = [
533
707
  "AbsMaxResult",
534
708
  "set_attr_generic",
535
709
  "get_attr",
710
+ "get_theta_attr",
536
711
  "collect_attr",
712
+ "collect_theta_attr",
537
713
  "set_attr",
538
714
  "get_attr_str",
539
715
  "set_attr_str",
716
+ "set_theta_attr",
540
717
  "set_attr_and_cache",
541
718
  "set_attr_with_max",
542
719
  "set_scalar",
tnfr/alias.pyi ADDED
@@ -0,0 +1,108 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterable, Mapping, MutableMapping
4
+ from types import ModuleType
5
+ from typing import TYPE_CHECKING, Any, Hashable, TypeVar
6
+
7
+ from .types import FloatArray, NodeId
8
+
9
+ if TYPE_CHECKING:
10
+ import networkx as nx
11
+
12
+ T = TypeVar("T")
13
+
14
+ __all__: list[str]
15
+
16
+ def __getattr__(name: str) -> Any: ...
17
+
18
+ class AbsMaxResult:
19
+ max_value: float
20
+ node: Hashable | None
21
+
22
+ SCALAR_SETTERS: dict[str, dict[str, Any]]
23
+
24
+ def get_attr(
25
+ d: dict[str, Any],
26
+ aliases: Iterable[str],
27
+ default: T | None = ...,
28
+ *,
29
+ strict: bool = ...,
30
+ log_level: int | None = ...,
31
+ conv: Callable[[Any], T] = ...,
32
+ ) -> T | None: ...
33
+ def get_theta_attr(
34
+ d: Mapping[str, Any],
35
+ default: T | None = ...,
36
+ *,
37
+ strict: bool = ...,
38
+ log_level: int | None = ...,
39
+ conv: Callable[[Any], T] = ...,
40
+ ) -> T | None: ...
41
+ def collect_attr(
42
+ G: "nx.Graph",
43
+ nodes: Iterable[NodeId],
44
+ aliases: Iterable[str],
45
+ default: float = ...,
46
+ *,
47
+ np: ModuleType | None = ...,
48
+ ) -> FloatArray | list[float]: ...
49
+ def collect_theta_attr(
50
+ G: "nx.Graph",
51
+ nodes: Iterable[NodeId],
52
+ default: float = ...,
53
+ *,
54
+ np: ModuleType | None = ...,
55
+ ) -> FloatArray | list[float]: ...
56
+ def set_attr_generic(
57
+ d: dict[str, Any],
58
+ aliases: Iterable[str],
59
+ value: Any,
60
+ *,
61
+ conv: Callable[[Any], T],
62
+ ) -> T: ...
63
+ def set_attr(
64
+ d: dict[str, Any],
65
+ aliases: Iterable[str],
66
+ value: Any,
67
+ conv: Callable[[Any], T] = ...,
68
+ ) -> T: ...
69
+ def get_attr_str(
70
+ d: dict[str, Any],
71
+ aliases: Iterable[str],
72
+ default: str | None = ...,
73
+ *,
74
+ strict: bool = ...,
75
+ log_level: int | None = ...,
76
+ conv: Callable[[Any], str] = ...,
77
+ ) -> str | None: ...
78
+ def set_attr_str(d: dict[str, Any], aliases: Iterable[str], value: Any) -> str: ...
79
+ def set_theta_attr(d: MutableMapping[str, Any], value: Any) -> float: ...
80
+ def multi_recompute_abs_max(
81
+ G: "nx.Graph", alias_map: Mapping[str, tuple[str, ...]]
82
+ ) -> dict[str, float]: ...
83
+ def set_attr_and_cache(
84
+ G: "nx.Graph",
85
+ n: Hashable,
86
+ aliases: tuple[str, ...],
87
+ value: float,
88
+ *,
89
+ cache: str | None = ...,
90
+ extra: Callable[["nx.Graph", Hashable, float], None] | None = ...,
91
+ ) -> AbsMaxResult | None: ...
92
+ def set_attr_with_max(
93
+ G: "nx.Graph", n: Hashable, aliases: tuple[str, ...], value: float, *, cache: str
94
+ ) -> AbsMaxResult: ...
95
+ def set_scalar(
96
+ G: "nx.Graph",
97
+ n: Hashable,
98
+ alias: tuple[str, ...],
99
+ value: float,
100
+ *,
101
+ cache: str | None = ...,
102
+ extra: Callable[["nx.Graph", Hashable, float], None] | None = ...,
103
+ ) -> AbsMaxResult | None: ...
104
+ def set_vf(
105
+ G: "nx.Graph", n: Hashable, value: float, *, update_max: bool = ...
106
+ ) -> AbsMaxResult | None: ...
107
+ def set_dnfr(G: "nx.Graph", n: Hashable, value: float) -> AbsMaxResult | None: ...
108
+ def set_theta(G: "nx.Graph", n: Hashable, value: float) -> AbsMaxResult | None: ...