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/dynamics/__init__.py CHANGED
@@ -1,658 +1,238 @@
1
- from __future__ import annotations
1
+ """Facade that keeps ΔNFR, νf and phase orchestration coherent across TNFR dynamics.
2
+
3
+ Attributes
4
+ ----------
5
+ run : callable
6
+ Callable that fully manages the evolution loop, integrating the nodal
7
+ equation while enforcing ΔNFR hooks, νf adaptation and phase coordination
8
+ on every step.
9
+ step : callable
10
+ Callable entry point for a single iteration that reuses the
11
+ ΔNFR/νf/phase pipeline while letting callers interleave bespoke telemetry
12
+ or operator injections.
13
+ set_delta_nfr_hook : callable
14
+ Callable used to install custom ΔNFR supervision under
15
+ ``G.graph['compute_delta_nfr']`` so each operator reorganization stays
16
+ coupled to νf drift and phase targets.
17
+ default_glyph_selector, parametric_glyph_selector : AbstractSelector
18
+ Selector implementations that choose glyphs according to ΔNFR trends,
19
+ νf ranges and phase synchrony, ensuring operator firing reinforces
20
+ coherence.
21
+ coordination, dnfr, integrators : module
22
+ Re-exported modules providing explicit control over phase alignment,
23
+ ΔNFR caches and integrator lifecycles to centralize orchestration.
24
+ ProcessPoolExecutor, apply_glyph, compute_Si : callable
25
+ Re-exported utilities for parallel selector evaluation, explicit glyph
26
+ execution and Si telemetry so ΔNFR, νf and phase traces remain observable.
27
+
28
+ Notes
29
+ -----
30
+ The facade aggregates runtime helpers that preserve canonical TNFR dynamics:
31
+ ``dnfr`` manages ΔNFR preparation and caching, ``integrators`` drives the
32
+ numerical updates of νf and EPI, and ``coordination`` synchronizes global and
33
+ local phase. Complementary exports such as
34
+ :func:`~tnfr.dynamics.adaptation.adapt_vf_by_coherence` and
35
+ :func:`~tnfr.dynamics.coordination.coordinate_global_local_phase` allow custom
36
+ feedback loops without breaking operator closure.
37
+
38
+ Examples
39
+ --------
40
+ >>> from tnfr.constants import DNFR_PRIMARY, EPI_PRIMARY, VF_PRIMARY
41
+ >>> from tnfr.structural import Coherence, Emission, Resonance, create_nfr, run_sequence
42
+ >>> from tnfr.dynamics import parametric_glyph_selector, run, set_delta_nfr_hook, step
43
+ >>> G, node = create_nfr("seed", epi=0.22, vf=1.0)
44
+ >>> def regulate_delta(graph, *, n_jobs=None):
45
+ ... for _, nd in graph.nodes(data=True):
46
+ ... delta = nd[VF_PRIMARY] * 0.08
47
+ ... nd[DNFR_PRIMARY] = delta
48
+ ... nd[EPI_PRIMARY] += delta
49
+ ... nd[VF_PRIMARY] += delta * 0.05
50
+ ... return None
51
+ >>> set_delta_nfr_hook(G, regulate_delta, note="ΔNFR guided by νf")
52
+ >>> G.graph["glyph_selector"] = parametric_glyph_selector
53
+ >>> run_sequence(G, node, [Emission(), Resonance(), Coherence()])
54
+ >>> run(G, steps=2, dt=0.05)
55
+ >>> # Automatic integration keeps ΔNFR, νf and phase co-modulated.
56
+ >>> step(G, dt=0.05)
57
+ >>> # Manual control reuses the selector state to consolidate coherence traces.
58
+ """
2
59
 
3
- import math
4
- from collections import deque
5
- from operator import itemgetter
6
- from typing import Any
60
+ from __future__ import annotations
7
61
 
8
- # Importar compute_Si y apply_glyph a nivel de módulo evita el coste de
9
- # realizar la importación en cada paso de la dinámica. Como los módulos de
10
- # origen no dependen de ``dynamics``, no se introducen ciclos.
11
- from ..operators import apply_remesh_if_globally_stable, apply_glyph
12
- from ..grammar import enforce_canonical_grammar, on_applied_glyph
13
- from ..types import Glyph
14
- from ..constants import (
15
- DEFAULTS,
16
- METRIC_DEFAULTS,
17
- get_aliases,
18
- get_param,
19
- get_graph_param,
20
- )
21
- from ..observers import DEFAULT_GLYPH_LOAD_SPAN, glyph_load, kuramoto_order
62
+ from concurrent.futures import ProcessPoolExecutor
22
63
 
23
- from ..helpers.numeric import (
24
- clamp,
25
- clamp01,
26
- angle_diff,
64
+ from ..metrics.sense_index import compute_Si
65
+ from ..operators import apply_glyph
66
+ from ..types import GlyphCode
67
+ from ..utils import get_numpy
68
+ from . import canonical, coordination, dnfr, integrators, metabolism
69
+ from .adaptation import adapt_vf_by_coherence
70
+ from .bifurcation import get_bifurcation_paths, compute_bifurcation_score
71
+ from .propagation import (
72
+ propagate_dissonance,
73
+ compute_network_dissonance_field,
74
+ detect_bifurcation_cascade,
27
75
  )
28
- from ..metrics.trig import neighbor_phase_mean
29
- from ..alias import (
30
- get_attr,
31
- set_vf,
32
- set_attr,
33
- set_theta,
34
- multi_recompute_abs_max,
76
+ from .aliases import (
77
+ ALIAS_D2EPI,
78
+ ALIAS_DNFR,
79
+ ALIAS_DSI,
80
+ ALIAS_EPI,
81
+ ALIAS_SI,
82
+ ALIAS_VF,
35
83
  )
36
- from ..metrics.sense_index import compute_Si
37
- from ..metrics.common import compute_dnfr_accel_max, merge_and_normalize_weights
38
- from ..metrics.trig_cache import compute_theta_trig
39
- from ..callback_utils import CallbackEvent, callback_manager
40
- from ..glyph_history import recent_glyph, ensure_history, append_metric
41
- from ..selector import (
42
- _selector_thresholds,
43
- _norms_para_selector,
44
- _calc_selector_score,
45
- _apply_selector_hysteresis,
84
+ from .canonical import (
85
+ NodalEquationResult,
86
+ compute_canonical_nodal_derivative,
87
+ validate_nodal_gradient,
88
+ validate_structural_frequency,
46
89
  )
47
-
48
- from .sampling import update_node_sample as _update_node_sample
90
+ from .coordination import coordinate_global_local_phase
49
91
  from .dnfr import (
50
- _prepare_dnfr_data,
92
+ _compute_dnfr,
93
+ _compute_neighbor_means,
51
94
  _init_dnfr_cache,
95
+ _prepare_dnfr_data,
52
96
  _refresh_dnfr_vectors,
53
- _compute_neighbor_means,
54
- _compute_dnfr,
55
97
  default_compute_delta_nfr,
56
- set_delta_nfr_hook,
57
- dnfr_phase_only,
58
98
  dnfr_epi_vf_mixed,
59
99
  dnfr_laplacian,
100
+ dnfr_phase_only,
101
+ set_delta_nfr_hook,
102
+ )
103
+ from .dynamic_limits import (
104
+ DynamicLimits,
105
+ DynamicLimitsConfig,
106
+ compute_dynamic_limits,
60
107
  )
61
108
  from .integrators import (
109
+ AbstractIntegrator,
110
+ DefaultIntegrator,
62
111
  prepare_integration_params,
63
112
  update_epi_via_nodal_equation,
64
113
  )
65
-
66
- ALIAS_VF = get_aliases("VF")
67
- ALIAS_THETA = get_aliases("THETA")
68
- ALIAS_DNFR = get_aliases("DNFR")
69
- ALIAS_EPI = get_aliases("EPI")
70
- ALIAS_SI = get_aliases("SI")
71
- ALIAS_D2EPI = get_aliases("D2EPI")
72
- ALIAS_DSI = get_aliases("DSI")
114
+ from .learning import AdaptiveLearningSystem
115
+ from .feedback import StructuralFeedbackLoop
116
+ from .adaptive_sequences import AdaptiveSequenceSelector
117
+ from .homeostasis import StructuralHomeostasis
118
+ from .structural_clip import (
119
+ structural_clip,
120
+ StructuralClipStats,
121
+ get_clip_stats,
122
+ reset_clip_stats,
123
+ )
124
+ from .runtime import (
125
+ _maybe_remesh,
126
+ _normalize_job_overrides,
127
+ _prepare_dnfr,
128
+ _resolve_jobs_override,
129
+ _run_after_callbacks,
130
+ _run_before_callbacks,
131
+ _run_validators,
132
+ _update_epi_hist,
133
+ _update_nodes,
134
+ run,
135
+ step,
136
+ )
137
+ from .sampling import update_node_sample as _update_node_sample
138
+ from .selectors import (
139
+ AbstractSelector,
140
+ DefaultGlyphSelector,
141
+ ParametricGlyphSelector,
142
+ _apply_glyphs,
143
+ _apply_selector,
144
+ _choose_glyph,
145
+ _collect_selector_metrics,
146
+ _configure_selector_weights,
147
+ _prepare_selector_preselection,
148
+ _resolve_preselected_glyph,
149
+ _selector_parallel_jobs,
150
+ _SelectorPreselection,
151
+ default_glyph_selector,
152
+ parametric_glyph_selector,
153
+ )
73
154
 
74
155
  __all__ = (
156
+ "canonical",
157
+ "coordination",
158
+ "dnfr",
159
+ "integrators",
160
+ "metabolism",
161
+ # Bifurcation dynamics
162
+ "get_bifurcation_paths",
163
+ "compute_bifurcation_score",
164
+ # Propagation dynamics
165
+ "propagate_dissonance",
166
+ "compute_network_dissonance_field",
167
+ "detect_bifurcation_cascade",
168
+ "ALIAS_D2EPI",
169
+ "ALIAS_DNFR",
170
+ "ALIAS_DSI",
171
+ "ALIAS_EPI",
172
+ "ALIAS_SI",
173
+ "ALIAS_VF",
174
+ "AbstractSelector",
175
+ "DefaultGlyphSelector",
176
+ "ParametricGlyphSelector",
177
+ "GlyphCode",
178
+ "_SelectorPreselection",
179
+ "_apply_glyphs",
180
+ "_apply_selector",
181
+ "_choose_glyph",
182
+ "_collect_selector_metrics",
183
+ "_configure_selector_weights",
184
+ "ProcessPoolExecutor",
185
+ "_maybe_remesh",
186
+ "_normalize_job_overrides",
187
+ "_prepare_dnfr",
188
+ "_prepare_dnfr_data",
189
+ "_prepare_selector_preselection",
190
+ "_resolve_jobs_override",
191
+ "_resolve_preselected_glyph",
192
+ "_run_after_callbacks",
193
+ "_run_before_callbacks",
194
+ "_run_validators",
195
+ "_selector_parallel_jobs",
196
+ "_update_epi_hist",
197
+ "_update_node_sample",
198
+ "_update_nodes",
199
+ "_compute_dnfr",
200
+ "_compute_neighbor_means",
201
+ "_init_dnfr_cache",
202
+ "_refresh_dnfr_vectors",
203
+ "adapt_vf_by_coherence",
204
+ "coordinate_global_local_phase",
205
+ "compute_Si",
206
+ "compute_canonical_nodal_derivative",
207
+ "NodalEquationResult",
208
+ "validate_nodal_gradient",
209
+ "validate_structural_frequency",
75
210
  "default_compute_delta_nfr",
76
- "set_delta_nfr_hook",
77
- "dnfr_phase_only",
211
+ "default_glyph_selector",
78
212
  "dnfr_epi_vf_mixed",
79
213
  "dnfr_laplacian",
80
- "prepare_integration_params",
81
- "update_epi_via_nodal_equation",
82
- "apply_canonical_clamps",
83
- "validate_canon",
84
- "coordinate_global_local_phase",
85
- "adapt_vf_by_coherence",
86
- "default_glyph_selector",
214
+ "dnfr_phase_only",
215
+ "get_numpy",
216
+ "apply_glyph",
87
217
  "parametric_glyph_selector",
88
- "step",
218
+ "AbstractIntegrator",
219
+ "DefaultIntegrator",
220
+ "prepare_integration_params",
89
221
  "run",
90
- "_prepare_dnfr_data",
91
- "_init_dnfr_cache",
92
- "_refresh_dnfr_vectors",
93
- "_compute_neighbor_means",
94
- "_compute_dnfr",
222
+ "set_delta_nfr_hook",
223
+ "step",
224
+ "update_epi_via_nodal_equation",
225
+ "DynamicLimits",
226
+ "DynamicLimitsConfig",
227
+ "compute_dynamic_limits",
228
+ "structural_clip",
229
+ "StructuralClipStats",
230
+ "get_clip_stats",
231
+ "reset_clip_stats",
232
+ "AdaptiveLearningSystem",
233
+ "StructuralFeedbackLoop",
234
+ "AdaptiveSequenceSelector",
235
+ "StructuralHomeostasis",
236
+ "get_bifurcation_paths",
237
+ "compute_bifurcation_score",
95
238
  )
96
-
97
-
98
- def _log_clamp(hist, node, attr, value, lo, hi):
99
- if value < lo or value > hi:
100
- hist.append({"node": node, "attr": attr, "value": float(value)})
101
-
102
-
103
- def apply_canonical_clamps(nd: dict[str, Any], G=None, node=None) -> None:
104
- g = G.graph if G is not None else DEFAULTS
105
- eps_min = float(g.get("EPI_MIN", DEFAULTS["EPI_MIN"]))
106
- eps_max = float(g.get("EPI_MAX", DEFAULTS["EPI_MAX"]))
107
- vf_min = float(g.get("VF_MIN", DEFAULTS["VF_MIN"]))
108
- vf_max = float(g.get("VF_MAX", DEFAULTS["VF_MAX"]))
109
- theta_wrap = bool(g.get("THETA_WRAP", DEFAULTS["THETA_WRAP"]))
110
-
111
- epi = get_attr(nd, ALIAS_EPI, 0.0)
112
- vf = get_attr(nd, ALIAS_VF, 0.0)
113
- th = get_attr(nd, ALIAS_THETA, 0.0)
114
-
115
- strict = bool(
116
- g.get("VALIDATORS_STRICT", DEFAULTS.get("VALIDATORS_STRICT", False))
117
- )
118
- if strict and G is not None:
119
- hist = g.setdefault("history", {}).setdefault("clamp_alerts", [])
120
- _log_clamp(hist, node, "EPI", epi, eps_min, eps_max)
121
- _log_clamp(hist, node, "VF", vf, vf_min, vf_max)
122
-
123
- set_attr(nd, ALIAS_EPI, clamp(epi, eps_min, eps_max))
124
-
125
- vf_val = clamp(vf, vf_min, vf_max)
126
- if G is not None and node is not None:
127
- set_vf(G, node, vf_val, update_max=False)
128
- else:
129
- set_attr(nd, ALIAS_VF, vf_val)
130
-
131
- if theta_wrap:
132
- new_th = (th + math.pi) % (2 * math.pi) - math.pi
133
- if G is not None and node is not None:
134
- set_theta(G, node, new_th)
135
- else:
136
- set_attr(nd, ALIAS_THETA, new_th)
137
-
138
-
139
- def validate_canon(G) -> None:
140
- """Apply canonical clamps to all nodes of ``G``.
141
-
142
- Wrap phase and constrain ``EPI`` and ``νf`` to the ranges in ``G.graph``.
143
- If ``VALIDATORS_STRICT`` is active, alerts are logged in ``history``.
144
- """
145
- for n, nd in G.nodes(data=True):
146
- apply_canonical_clamps(nd, G, n)
147
- maxes = multi_recompute_abs_max(G, {"_vfmax": ALIAS_VF})
148
- G.graph.update(maxes)
149
- return G
150
-
151
-
152
- def _read_adaptive_params(
153
- g: dict[str, Any],
154
- ) -> tuple[dict[str, Any], float, float]:
155
- """Obtain configuration and current values for phase adaptation."""
156
- cfg = g.get("PHASE_ADAPT", DEFAULTS.get("PHASE_ADAPT", {}))
157
- kG = float(g.get("PHASE_K_GLOBAL", DEFAULTS["PHASE_K_GLOBAL"]))
158
- kL = float(g.get("PHASE_K_LOCAL", DEFAULTS["PHASE_K_LOCAL"]))
159
- return cfg, kG, kL
160
-
161
-
162
- def _compute_state(G, cfg: dict[str, Any]) -> tuple[str, float, float]:
163
- """Return current state (stable/dissonant/transition) and metrics."""
164
- R = kuramoto_order(G)
165
- dist = glyph_load(G, window=DEFAULT_GLYPH_LOAD_SPAN)
166
- disr = float(dist.get("_disruptivos", 0.0)) if dist else 0.0
167
-
168
- R_hi = float(cfg.get("R_hi", 0.90))
169
- R_lo = float(cfg.get("R_lo", 0.60))
170
- disr_hi = float(cfg.get("disr_hi", 0.50))
171
- disr_lo = float(cfg.get("disr_lo", 0.25))
172
- if (R >= R_hi) and (disr <= disr_lo):
173
- state = "estable"
174
- elif (R <= R_lo) or (disr >= disr_hi):
175
- state = "disonante"
176
- else:
177
- state = "transicion"
178
- return state, float(R), disr
179
-
180
-
181
- def _smooth_adjust_k(
182
- kG: float, kL: float, state: str, cfg: dict[str, Any]
183
- ) -> tuple[float, float]:
184
- """Smoothly update kG/kL toward targets according to state."""
185
- kG_min = float(cfg.get("kG_min", 0.01))
186
- kG_max = float(cfg.get("kG_max", 0.20))
187
- kL_min = float(cfg.get("kL_min", 0.05))
188
- kL_max = float(cfg.get("kL_max", 0.25))
189
-
190
- if state == "disonante":
191
- kG_t = kG_max
192
- kL_t = 0.5 * (
193
- kL_min + kL_max
194
- ) # local medio para no perder plasticidad
195
- elif state == "estable":
196
- kG_t = kG_min
197
- kL_t = kL_min
198
- else:
199
- kG_t = 0.5 * (kG_min + kG_max)
200
- kL_t = 0.5 * (kL_min + kL_max)
201
-
202
- up = float(cfg.get("up", 0.10))
203
- down = float(cfg.get("down", 0.07))
204
-
205
- def _step(curr: float, target: float, mn: float, mx: float) -> float:
206
- gain = up if target > curr else down
207
- nxt = curr + gain * (target - curr)
208
- return max(mn, min(mx, nxt))
209
-
210
- return _step(kG, kG_t, kG_min, kG_max), _step(kL, kL_t, kL_min, kL_max)
211
-
212
-
213
- def _ensure_hist_deque(hist: dict[str, Any], key: str, maxlen: int) -> deque:
214
- """Ensure history entry ``key`` is a deque with ``maxlen``."""
215
- dq = hist.setdefault(key, deque(maxlen=maxlen))
216
- if not isinstance(dq, deque):
217
- dq = deque(dq, maxlen=maxlen)
218
- hist[key] = dq
219
- return dq
220
-
221
-
222
- def coordinate_global_local_phase(
223
- G, global_force: float | None = None, local_force: float | None = None
224
- ) -> None:
225
- """
226
- Ajusta fase con mezcla GLOBAL+VECINAL.
227
- Si no se pasan fuerzas explícitas, adapta kG/kL según estado
228
- (disonante / transición / estable).
229
- Estado se decide por R (Kuramoto) y carga glífica disruptiva reciente.
230
- """
231
- g = G.graph
232
- defaults = DEFAULTS
233
- hist = g.setdefault("history", {})
234
- maxlen = int(
235
- g.get("PHASE_HISTORY_MAXLEN", METRIC_DEFAULTS["PHASE_HISTORY_MAXLEN"])
236
- )
237
- hist_state = _ensure_hist_deque(hist, "phase_state", maxlen)
238
- hist_R = _ensure_hist_deque(hist, "phase_R", maxlen)
239
- hist_disr = _ensure_hist_deque(hist, "phase_disr", maxlen)
240
- # 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
241
- if (global_force is not None) or (local_force is not None):
242
- kG = float(
243
- global_force
244
- if global_force is not None
245
- else g.get("PHASE_K_GLOBAL", defaults["PHASE_K_GLOBAL"])
246
- )
247
- kL = float(
248
- local_force
249
- if local_force is not None
250
- else g.get("PHASE_K_LOCAL", defaults["PHASE_K_LOCAL"])
251
- )
252
- else:
253
- cfg, kG, kL = _read_adaptive_params(g)
254
-
255
- if bool(cfg.get("enabled", False)):
256
- state, R, disr = _compute_state(G, cfg)
257
- kG, kL = _smooth_adjust_k(kG, kL, state, cfg)
258
-
259
- hist_state.append(state)
260
- hist_R.append(float(R))
261
- hist_disr.append(float(disr))
262
-
263
- g["PHASE_K_GLOBAL"] = kG
264
- g["PHASE_K_LOCAL"] = kL
265
- append_metric(hist, "phase_kG", float(kG))
266
- append_metric(hist, "phase_kL", float(kL))
267
-
268
- # 6) Fase GLOBAL (centroide) para empuje
269
- trig = compute_theta_trig(G.nodes(data=True))
270
- num_nodes = G.number_of_nodes()
271
- if num_nodes:
272
- mean_cos = sum(trig.cos.values()) / num_nodes
273
- mean_sin = sum(trig.sin.values()) / num_nodes
274
- thG = math.atan2(mean_sin, mean_cos)
275
- else:
276
- thG = 0.0
277
-
278
- # 7) Aplicar corrección global+vecinal
279
- for n, nd in G.nodes(data=True):
280
- th = get_attr(nd, ALIAS_THETA, 0.0)
281
- thL = neighbor_phase_mean(G, n)
282
- dG = angle_diff(thG, th)
283
- dL = angle_diff(thL, th)
284
- set_theta(G, n, th + kG * dG + kL * dL)
285
-
286
-
287
- # -------------------------
288
- # Adaptación de νf por coherencia
289
- # -------------------------
290
-
291
-
292
- def adapt_vf_by_coherence(G) -> None:
293
- """Adjust νf toward neighbour mean in nodes with sustained stability."""
294
- tau = get_graph_param(G, "VF_ADAPT_TAU", int)
295
- mu = get_graph_param(G, "VF_ADAPT_MU")
296
- eps_dnfr = get_graph_param(G, "EPS_DNFR_STABLE")
297
- thr_sel = get_graph_param(G, "SELECTOR_THRESHOLDS", dict)
298
- thr_def = get_graph_param(G, "GLYPH_THRESHOLDS", dict)
299
- si_hi = float(thr_sel.get("si_hi", thr_def.get("hi", 0.66)))
300
- vf_min = get_graph_param(G, "VF_MIN")
301
- vf_max = get_graph_param(G, "VF_MAX")
302
-
303
- updates = {}
304
- for n, nd in G.nodes(data=True):
305
- Si = get_attr(nd, ALIAS_SI, 0.0)
306
- dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0))
307
- if Si >= si_hi and dnfr <= eps_dnfr:
308
- nd["stable_count"] = nd.get("stable_count", 0) + 1
309
- else:
310
- nd["stable_count"] = 0
311
- continue
312
-
313
- if nd["stable_count"] >= tau:
314
- vf = get_attr(nd, ALIAS_VF, 0.0)
315
- neigh = list(G.neighbors(n))
316
- if neigh:
317
- total = 0.0
318
- for v in neigh:
319
- total += float(get_attr(G.nodes[v], ALIAS_VF, vf))
320
- vf_bar = total / len(neigh)
321
- else:
322
- vf_bar = float(vf)
323
- updates[n] = vf + mu * (vf_bar - vf)
324
-
325
- for n, vf_new in updates.items():
326
- set_vf(G, n, clamp(vf_new, vf_min, vf_max))
327
-
328
-
329
- # -------------------------
330
- # Selector glífico por defecto
331
- # -------------------------
332
- def default_glyph_selector(G, n) -> str:
333
- nd = G.nodes[n]
334
- thr = _selector_thresholds(G)
335
- hi, lo, dnfr_hi = itemgetter("si_hi", "si_lo", "dnfr_hi")(thr)
336
- # Extract thresholds in one call to reduce dict lookups inside loops.
337
-
338
- norms = G.graph.get("_sel_norms")
339
- if norms is None:
340
- norms = compute_dnfr_accel_max(G)
341
- G.graph["_sel_norms"] = norms
342
- dnfr_max = float(norms.get("dnfr_max", 1.0)) or 1.0
343
-
344
- Si = clamp01(get_attr(nd, ALIAS_SI, 0.5))
345
- dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0)) / dnfr_max
346
-
347
- if Si >= hi:
348
- return "IL"
349
- if Si <= lo:
350
- return "OZ" if dnfr > dnfr_hi else "ZHIR"
351
- return "NAV" if dnfr > dnfr_hi else "RA"
352
-
353
-
354
- # -------------------------
355
- # Selector glífico multiobjetivo (paramétrico)
356
- # -------------------------
357
- def _soft_grammar_prefilter(G, n, cand, dnfr, accel):
358
- """Soft grammar: avoid repetitions before the canonical one."""
359
- gram = get_graph_param(G, "GRAMMAR", dict)
360
- gwin = int(gram.get("window", 3))
361
- avoid = set(gram.get("avoid_repeats", []))
362
- force_dn = float(gram.get("force_dnfr", 0.60))
363
- force_ac = float(gram.get("force_accel", 0.60))
364
- fallbacks = gram.get("fallbacks", {})
365
- nd = G.nodes[n]
366
- if cand in avoid and recent_glyph(nd, cand, gwin):
367
- if not (dnfr >= force_dn or accel >= force_ac):
368
- cand = fallbacks.get(cand, cand)
369
- return cand
370
-
371
-
372
- def _selector_normalized_metrics(nd, norms):
373
- """Extract and normalise Si, ΔNFR and acceleration for the selector."""
374
- dnfr_max = float(norms.get("dnfr_max", 1.0)) or 1.0
375
- acc_max = float(norms.get("accel_max", 1.0)) or 1.0
376
- Si = clamp01(get_attr(nd, ALIAS_SI, 0.5))
377
- dnfr = abs(get_attr(nd, ALIAS_DNFR, 0.0)) / dnfr_max
378
- accel = abs(get_attr(nd, ALIAS_D2EPI, 0.0)) / acc_max
379
- return Si, dnfr, accel
380
-
381
-
382
- def _selector_base_choice(Si, dnfr, accel, thr):
383
- """Base decision according to thresholds of Si, ΔNFR and acceleration."""
384
- si_hi, si_lo, dnfr_hi, acc_hi = itemgetter(
385
- "si_hi", "si_lo", "dnfr_hi", "accel_hi"
386
- )(thr) # Reduce dict lookups inside loops.
387
- if Si >= si_hi:
388
- return "IL"
389
- if Si <= si_lo:
390
- if accel >= acc_hi:
391
- return "THOL"
392
- return "OZ" if dnfr >= dnfr_hi else "ZHIR"
393
- if dnfr >= dnfr_hi or accel >= acc_hi:
394
- return "NAV"
395
- return "RA"
396
-
397
-
398
- def _configure_selector_weights(G) -> dict:
399
- """Normalise and store selector weights in ``G.graph``."""
400
- weights = merge_and_normalize_weights(
401
- G, "SELECTOR_WEIGHTS", ("w_si", "w_dnfr", "w_accel")
402
- )
403
- G.graph["_selector_weights"] = weights
404
- return weights
405
-
406
-
407
- def _compute_selector_score(G, nd, Si, dnfr, accel, cand):
408
- """Compute score and apply stagnation penalties."""
409
- W = G.graph.get("_selector_weights")
410
- if W is None:
411
- W = _configure_selector_weights(G)
412
- score = _calc_selector_score(Si, dnfr, accel, W)
413
- hist_prev = nd.get("glyph_history")
414
- if hist_prev and hist_prev[-1] == cand:
415
- delta_si = get_attr(nd, ALIAS_DSI, 0.0)
416
- h = ensure_history(G)
417
- sig = h.get("sense_sigma_mag", [])
418
- delta_sigma = sig[-1] - sig[-2] if len(sig) >= 2 else 0.0
419
- if delta_si <= 0.0 and delta_sigma <= 0.0:
420
- score -= 0.05
421
- return score
422
-
423
-
424
- def _apply_score_override(cand, score, dnfr, dnfr_lo):
425
- """Adjust final candidate smoothly according to the score."""
426
- if score >= 0.66 and cand in ("NAV", "RA", "ZHIR", "OZ"):
427
- cand = "IL"
428
- elif score <= 0.33 and cand in ("NAV", "RA", "IL"):
429
- cand = "OZ" if dnfr >= dnfr_lo else "ZHIR"
430
- return cand
431
-
432
-
433
- def parametric_glyph_selector(G, n) -> str:
434
- """Multiobjective: combine Si, |ΔNFR|_norm and |accel|_norm with
435
- hysteresis.
436
-
437
- Base rules:
438
- - High Si ⇒ IL
439
- - Low Si ⇒ OZ if |ΔNFR| high; ZHIR if |ΔNFR| low;
440
- THOL if acceleration is high
441
- - Medium Si ⇒ NAV if |ΔNFR| high (or acceleration high),
442
- otherwise RA
443
- """
444
- nd = G.nodes[n]
445
- thr = _selector_thresholds(G)
446
- margin = get_graph_param(G, "GLYPH_SELECTOR_MARGIN")
447
-
448
- norms = G.graph.get("_sel_norms") or _norms_para_selector(G)
449
- Si, dnfr, accel = _selector_normalized_metrics(nd, norms)
450
-
451
- cand = _selector_base_choice(Si, dnfr, accel, thr)
452
-
453
- hist_cand = _apply_selector_hysteresis(nd, Si, dnfr, accel, thr, margin)
454
- if hist_cand is not None:
455
- return hist_cand
456
-
457
- score = _compute_selector_score(G, nd, Si, dnfr, accel, cand)
458
-
459
- cand = _apply_score_override(cand, score, dnfr, thr["dnfr_lo"])
460
-
461
- return _soft_grammar_prefilter(G, n, cand, dnfr, accel)
462
-
463
-
464
- def _choose_glyph(G, n, selector, use_canon, h_al, h_en, al_max, en_max):
465
- """Select the glyph to apply on node ``n``."""
466
- if h_al[n] > al_max:
467
- return Glyph.AL
468
- if h_en[n] > en_max:
469
- return Glyph.EN
470
- g = selector(G, n)
471
- if use_canon:
472
- g = enforce_canonical_grammar(G, n, g)
473
- return g
474
-
475
-
476
- # -------------------------
477
- # Step / run
478
- # -------------------------
479
-
480
-
481
- def _run_before_callbacks(
482
- G, *, step_idx: int, dt: float | None, use_Si: bool, apply_glyphs: bool
483
- ) -> None:
484
- callback_manager.invoke_callbacks(
485
- G,
486
- CallbackEvent.BEFORE_STEP.value,
487
- {
488
- "step": step_idx,
489
- "dt": dt,
490
- "use_Si": use_Si,
491
- "apply_glyphs": apply_glyphs,
492
- },
493
- )
494
-
495
-
496
- def _prepare_dnfr(G, *, use_Si: bool) -> None:
497
- """Compute ΔNFR and optionally Si for the current graph state."""
498
- compute_dnfr_cb = G.graph.get(
499
- "compute_delta_nfr", default_compute_delta_nfr
500
- )
501
- compute_dnfr_cb(G)
502
- G.graph.pop("_sel_norms", None)
503
- if use_Si:
504
- compute_Si(G, inplace=True)
505
-
506
-
507
- def _apply_selector(G):
508
- """Configure and return the glyph selector for this step."""
509
- selector = G.graph.get("glyph_selector", default_glyph_selector)
510
- if selector is parametric_glyph_selector:
511
- _norms_para_selector(G)
512
- _configure_selector_weights(G)
513
- return selector
514
-
515
-
516
- def _apply_glyphs(G, selector, hist) -> None:
517
- """Apply glyphs to nodes using ``selector`` and update history."""
518
- window = int(get_param(G, "GLYPH_HYSTERESIS_WINDOW"))
519
- use_canon = bool(
520
- get_graph_param(G, "GRAMMAR_CANON", dict).get("enabled", False)
521
- )
522
- al_max = get_graph_param(G, "AL_MAX_LAG", int)
523
- en_max = get_graph_param(G, "EN_MAX_LAG", int)
524
- h_al = hist.setdefault("since_AL", {})
525
- h_en = hist.setdefault("since_EN", {})
526
- for n, _ in G.nodes(data=True):
527
- h_al[n] = int(h_al.get(n, 0)) + 1
528
- h_en[n] = int(h_en.get(n, 0)) + 1
529
- g = _choose_glyph(
530
- G, n, selector, use_canon, h_al, h_en, al_max, en_max
531
- )
532
- apply_glyph(G, n, g, window=window)
533
- if use_canon:
534
- on_applied_glyph(G, n, g)
535
- if g == Glyph.AL:
536
- h_al[n] = 0
537
- h_en[n] = min(h_en[n], en_max)
538
- elif g == Glyph.EN:
539
- h_en[n] = 0
540
-
541
-
542
- def _update_nodes(
543
- G,
544
- *,
545
- dt: float | None,
546
- use_Si: bool,
547
- apply_glyphs: bool,
548
- step_idx: int,
549
- hist,
550
- ) -> None:
551
- _update_node_sample(G, step=step_idx)
552
- _prepare_dnfr(G, use_Si=use_Si)
553
- selector = _apply_selector(G)
554
- if apply_glyphs:
555
- _apply_glyphs(G, selector, hist)
556
- _dt = get_graph_param(G, "DT") if dt is None else float(dt)
557
- method = get_graph_param(G, "INTEGRATOR_METHOD", str)
558
- update_epi_via_nodal_equation(G, dt=_dt, method=method)
559
- for n, nd in G.nodes(data=True):
560
- apply_canonical_clamps(nd, G, n)
561
- coordinate_global_local_phase(G, None, None)
562
- adapt_vf_by_coherence(G)
563
-
564
-
565
- def _update_epi_hist(G) -> None:
566
- tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
567
- tau_l = int(get_param(G, "REMESH_TAU_LOCAL"))
568
- tau = max(tau_g, tau_l)
569
- maxlen = max(2 * tau + 5, 64)
570
- epi_hist = G.graph.get("_epi_hist")
571
- if not isinstance(epi_hist, deque) or epi_hist.maxlen != maxlen:
572
- epi_hist = deque(list(epi_hist or [])[-maxlen:], maxlen=maxlen)
573
- G.graph["_epi_hist"] = epi_hist
574
- epi_hist.append(
575
- {n: get_attr(nd, ALIAS_EPI, 0.0) for n, nd in G.nodes(data=True)}
576
- )
577
-
578
-
579
- def _maybe_remesh(G) -> None:
580
- apply_remesh_if_globally_stable(G)
581
-
582
-
583
- def _run_validators(G) -> None:
584
- from ..validators import run_validators
585
-
586
- run_validators(G)
587
-
588
-
589
- def _run_after_callbacks(G, *, step_idx: int) -> None:
590
- h = ensure_history(G)
591
- ctx = {"step": step_idx}
592
- metric_pairs = [
593
- ("C", "C_steps"),
594
- ("stable_frac", "stable_frac"),
595
- ("phase_sync", "phase_sync"),
596
- ("glyph_disr", "glyph_load_disr"),
597
- ("Si_mean", "Si_mean"),
598
- ]
599
- for dst, src in metric_pairs:
600
- values = h.get(src)
601
- if values:
602
- ctx[dst] = values[-1]
603
- callback_manager.invoke_callbacks(G, CallbackEvent.AFTER_STEP.value, ctx)
604
-
605
-
606
- def step(
607
- G,
608
- *,
609
- dt: float | None = None,
610
- use_Si: bool = True,
611
- apply_glyphs: bool = True,
612
- ) -> None:
613
- hist = ensure_history(G)
614
- step_idx = len(hist.setdefault("C_steps", []))
615
- _run_before_callbacks(
616
- G, step_idx=step_idx, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs
617
- )
618
- _update_nodes(
619
- G,
620
- dt=dt,
621
- use_Si=use_Si,
622
- apply_glyphs=apply_glyphs,
623
- step_idx=step_idx,
624
- hist=hist,
625
- )
626
- _update_epi_hist(G)
627
- _maybe_remesh(G)
628
- _run_validators(G)
629
- _run_after_callbacks(G, step_idx=step_idx)
630
-
631
-
632
- def run(
633
- G,
634
- steps: int,
635
- *,
636
- dt: float | None = None,
637
- use_Si: bool = True,
638
- apply_glyphs: bool = True,
639
- ) -> None:
640
- steps_int = int(steps)
641
- if steps_int < 0:
642
- raise ValueError("'steps' must be non-negative")
643
- stop_cfg = get_graph_param(G, "STOP_EARLY", dict)
644
- stop_enabled = False
645
- if stop_cfg and stop_cfg.get("enabled", False):
646
- w = int(stop_cfg.get("window", 25))
647
- frac = float(stop_cfg.get("fraction", 0.90))
648
- stop_enabled = True
649
- for _ in range(steps_int):
650
- step(G, dt=dt, use_Si=use_Si, apply_glyphs=apply_glyphs)
651
- # Early-stop opcional
652
- if stop_enabled:
653
- history = ensure_history(G)
654
- series = history.get("stable_frac", [])
655
- if not isinstance(series, list):
656
- series = list(series)
657
- if len(series) >= w and all(v >= frac for v in series[-w:]):
658
- break