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/operators/remesh.py CHANGED
@@ -1,27 +1,1146 @@
1
+ """Canonical REMESH operator: Recursive pattern propagation preserving structural coherence.
2
+
3
+ REMESH (Recursivity) - Glyph: REMESH
4
+ ====================================
5
+
6
+ Physical Foundation
7
+ -------------------
8
+ From the nodal equation: ∂EPI/∂t = νf · ΔNFR(t)
9
+
10
+ REMESH implements: **EPI(t) ↔ EPI(t-τ)** (operational fractality)
11
+
12
+ REMESH enables patterns to echo across temporal and spatial scales while maintaining
13
+ coherence. "What persists at one scale can be rewritten at another, with coherence
14
+ propagating structurally, not imposed." - El pulso que nos atraviesa
15
+
16
+ Canonical Physical Behavior
17
+ ----------------------------
18
+ REMESH acts on the nodal equation by creating temporal/spatial coupling:
19
+
20
+ 1. **Memory Activation**: References EPI(t-τ) from structural history
21
+ 2. **Pattern Recognition**: Identifies similar EPIs across network (structural_similarity)
22
+ 3. **Coherent Propagation**: Propagates patterns maintaining identity (StructuralIdentity)
23
+ 4. **Scale Invariance**: Preserves structural properties across reorganizations
24
+
25
+ Effect on nodal components:
26
+ - EPI: Mixed with historical states (EPI_new = (1-α)·EPI_now + α·EPI_past)
27
+ - νf: Can increase during memory activation (reactivation of dormant patterns)
28
+ - ΔNFR: Implicitly calculated from reorganization (ΔNFR = ΔEPI/νf from nodal equation)
29
+ - Phase: Preserved through StructuralIdentity tracking
30
+
31
+ Operator Relationships (from Nodal Equation Physics)
32
+ -----------------------------------------------------
33
+ All relationships emerge naturally from how operators affect EPI, νf, ΔNFR, and phase:
34
+
35
+ ### REMESH Hierarchical (Central Control → Periphery)
36
+ **Physics**: Controlled replication from center maintaining coherence descendente
37
+
38
+ 1. **IL (Coherence)**: Reduces |ΔNFR| → stabilizes each recursion level
39
+ - Relationship: Estabilización multinivel (coherence extended)
40
+ - Dynamics: REMESH propagates pattern, IL consolidates at each level
41
+ - Sequence: REMESH → IL (recursive propagation → multi-level stabilization)
42
+
43
+ 2. **VAL (Expansion)**: Increases dim(EPI) → structural expansion
44
+ - Relationship: Expansión estructural coherente (fractal growth)
45
+ - Dynamics: REMESH replicates, VAL expands each replica maintaining form
46
+ - Sequence: VAL → REMESH (expand → replicate expanded form)
47
+
48
+ 3. **SHA (Silence)**: νf → 0 → latent memory
49
+ - Relationship: Estabilización de red latente (structural memory)
50
+ - Dynamics: SHA freezes pattern (∂EPI/∂t → 0), REMESH propagates frozen state
51
+ - Sequence: SHA → REMESH (freeze → propagate frozen memory)
52
+ - **Critical**: NO functional redundancy - uses existing Silence operator
53
+
54
+ 4. **NUL (Contraction)**: Reduces dim(EPI) → compression
55
+ - Relationship: Compresión estructural coherente (fractal distillation)
56
+ - Dynamics: Complementary to VAL, reduces dimensionality maintaining identity
57
+ - Sequence: NUL → REMESH (compress → replicate compressed essence)
58
+ - **Note**: Hierarchical simplification preserving core structure
59
+
60
+ ### REMESH Rhizomatic (Decentralized Propagation)
61
+ **Physics**: Propagation without fixed center, by local resonance
62
+
63
+ 1. **OZ (Dissonance)**: Increases |ΔNFR| → exploration
64
+ - Relationship: Bifurcación distribuida (distributed bifurcation)
65
+ - Dynamics: REMESH + OZ creates decentralized local variations
66
+ - Sequence: OZ → REMESH (destabilize → replicate variations)
67
+
68
+ 2. **UM (Coupling)**: φᵢ → φⱼ → structural connection
69
+ - Relationship: Acoplamiento multiescala (multi-scale coupling)
70
+ - Dynamics: REMESH propagates, UM connects replicas without hierarchy
71
+ - Sequence: REMESH → UM (propagate → connect peers)
72
+
73
+ 3. **THOL (Self-organization)**: Creates sub-EPIs → emergence
74
+ - Relationship: Auto-organización recursiva (recursive self-organization)
75
+ - Dynamics: REMESH + THOL generates emergent structures without center
76
+ - Sequence: THOL → REMESH (emerge sub-EPIs → replicate emergent forms)
77
+
78
+ ### REMESH Fractal Harmonic (Perfect Self-Similarity)
79
+ **Physics**: Scale-symmetric replication maintaining perfect auto-similitud
80
+
81
+ 1. **RA (Resonance)**: Amplifies coherently → propagation
82
+ - Relationship: Resonancia multiescala (multi-scale resonance)
83
+ - Dynamics: REMESH replicates, RA amplifies with perfect symmetry
84
+ - Sequence: REMESH → RA (replicate → amplify symmetrically)
85
+
86
+ 2. **NAV (Transition)**: Activates latent EPI → regime shift
87
+ - Relationship: Transición entre attractores fractales
88
+ - Dynamics: REMESH navigates between self-similar attractor states
89
+ - Sequence: NAV → REMESH (transition → replicate new regime)
90
+
91
+ 3. **AL (Emission)**: Creates EPI from vacuum → generation
92
+ - Relationship: Emisión fractal (fractal emission)
93
+ - Dynamics: REMESH + AL generates self-similar patterns from origin
94
+ - Sequence: AL → REMESH (emit seed → replicate fractally)
95
+
96
+ 4. **EN (Reception)**: Updates EPI from network → reception
97
+ - Relationship: Recepción multi-escala simétrica (symmetric multi-scale reception)
98
+ - Dynamics: EN captures patterns from multiple sources → REMESH replicates symmetrically
99
+ - Sequence: EN → REMESH (receive multi-scale → propagate symmetrically)
100
+ - **Note**: Pre-recursion operator that feeds REMESH
101
+
102
+ ### Operators with Indirect Relationships
103
+
104
+ **ZHIR (Mutation)**: Present in canonical relationships but NOT in types
105
+ - **Physical Reason**: ZHIR is a TRANSFORMER that emerges POST-recursion
106
+ - **Dynamics**: REMESH propagates → local variations + destabilizers → ZHIR transforms
107
+ - **Grammar**: Requires IL previo + recent destabilizer (U4b)
108
+ - **Relationship**: Mutación replicativa (replication facilitates mutation)
109
+ - **Conclusion**: Operates AFTER REMESH completes, not during
110
+
111
+ Grammar Implications from Physical Analysis
112
+ --------------------------------------------
113
+ REMESH's physical behavior affects unified grammar rules (UNIFIED_GRAMMAR_RULES.md):
114
+
115
+ ### U1: STRUCTURAL INITIATION & CLOSURE
116
+ **Physical Basis**: REMESH echoes EPI(t-τ), can activate from dormant/null states
117
+
118
+ **U1a (Initiation)**: REMESH is a GENERATOR
119
+ - Can start sequences when operating on latent structure
120
+ - Activates dormant patterns via temporal coupling
121
+ - **Rule**: Sequences can begin with REMESH
122
+
123
+ **U1b (Closure)**: REMESH is a CLOSURE operator
124
+ - Distributes structure across scales leaving system in recursive attractor
125
+ - Creates self-sustaining multi-scale coherence
126
+ - **Rule**: Sequences can end with REMESH
127
+
128
+ ### U2: CONVERGENCE & BOUNDEDNESS
129
+ **Physical Basis**: REMESH mixing with historical states can amplify or dampen ΔNFR
130
+
131
+ **Requirement**: REMESH + destabilizers → must include stabilizers
132
+ - Example: REMESH + VAL (expansion) → requires IL (coherence)
133
+ - Prevents: Unbounded growth through recursive expansion
134
+ - **Rule**: If REMESH precedes/follows VAL, OZ, or ZHIR → require IL or THOL
135
+
136
+ **Integration Convergence**: ∫ νf · ΔNFR dt must converge
137
+ - REMESH temporal mixing: (1-α)·EPI_now + α·EPI_past
138
+ - Without stabilizers: Can create positive feedback loop
139
+ - **Rule**: Stabilizers ensure convergence of recursive reorganization
140
+
141
+ ### U3: RESONANT COUPLING
142
+ **Physical Basis**: REMESH propagates patterns - must verify phase compatibility
143
+
144
+ **Requirement**: REMESH with UM or RA → verify |φᵢ - φⱼ| ≤ Δφ_max
145
+ - REMESH creates replicas that must be phase-compatible for resonance
146
+ - Antiphase replicas → destructive interference
147
+ - **Rule**: StructuralIdentity.matches() includes phase verification
148
+ - **Implementation**: Phase pattern captured and validated during propagation
149
+
150
+ ### U4: BIFURCATION DYNAMICS
151
+ **Physical Basis**: REMESH can trigger bifurcation through recursive amplification
152
+
153
+ **U4a (Triggers Need Handlers)**: REMESH + destabilizers → need handlers
154
+ - REMESH → THOL sequence: Recursion enables self-organization
155
+ - Must handle emergent sub-EPIs from recursive bifurcation
156
+ - **Rule**: REMESH + OZ or ZHIR → require THOL or IL handlers
157
+
158
+ **U4b (Transformers Need Context)**: ZHIR requires REMESH context
159
+ - REMESH creates variations across scales
160
+ - ZHIR then transforms local variations (post-recursion)
161
+ - **Rule**: ZHIR after REMESH → requires prior IL + recent destabilizer
162
+
163
+ Centralized Flow - No Redundancy
164
+ ---------------------------------
165
+ This implementation maintains a single, centralized flow:
166
+
167
+ 1. **SHA Integration**: Uses existing Silence operator from definitions.py
168
+ - NO reimplementation of SHA functionality
169
+ - StructuralIdentity only CAPTURES frozen states, doesn't freeze
170
+ - Workflow: Silence() → capture_from_node(is_sha_frozen=True) → validate
171
+
172
+ 2. **Coherence Calculation**: Simplified C(t) for REMESH validation
173
+ - Full coherence calculation in tnfr.metrics module
174
+ - compute_global_coherence() specific to REMESH fidelity checks
175
+ - No duplication with main coherence computation
176
+
177
+ 3. **Pattern Recognition**: Unique to REMESH structural memory
178
+ - structural_similarity(): Pattern matching (no operator overlap)
179
+ - structural_memory_match(): Network-wide search (REMESH-specific)
180
+ - No duplication with network analysis tools
181
+
182
+ 4. **Identity Tracking**: REMESH-specific fractal identity
183
+ - StructuralIdentity: Persistent identity across reorganizations
184
+ - Captures EPI signature, νf range, phase pattern
185
+ - Validates preservation post-recursion
186
+
187
+ Key Capabilities
188
+ ----------------
189
+ - Structural memory: Pattern recognition across network nodes
190
+ - Identity preservation: Fractal lineage tracking across reorganizations
191
+ - Coherence conservation: Validating structural fidelity during remeshing
192
+ - Multi-modal recursivity: Hierarchical, rhizomatic, and fractal harmonic modes
193
+ - Grammar-compliant: All operations respect unified grammar rules (U1-U4)
194
+ """
195
+
1
196
  from __future__ import annotations
197
+
2
198
  import hashlib
3
199
  import heapq
4
- from operator import ge, le
200
+ import random
201
+ from collections import deque
202
+ from collections.abc import Hashable, Iterable, Mapping, MutableMapping, Sequence
203
+ from dataclasses import dataclass, field
5
204
  from functools import cache
6
- from itertools import combinations
7
205
  from io import StringIO
8
- from collections import deque
9
- from statistics import fmean, StatisticsError
206
+ from itertools import combinations
207
+ from operator import ge, le
208
+ from statistics import StatisticsError, fmean
209
+ from types import ModuleType
210
+ from typing import Any, cast
10
211
 
11
- from ..cache import edge_version_update
12
- from ..constants import DEFAULTS, REMESH_DEFAULTS, get_aliases, get_param
13
- from ..helpers.numeric import kahan_sum_nd
212
+ from .._compat import TypeAlias
14
213
  from ..alias import get_attr, set_attr
214
+ from ..constants import DEFAULTS, REMESH_DEFAULTS, get_param
215
+ from ..constants.aliases import ALIAS_EPI, ALIAS_VF, ALIAS_DNFR
15
216
  from ..rng import make_rng
16
- from ..callback_utils import CallbackEvent, callback_manager
17
- from ..glyph_history import append_metric, ensure_history, current_step_idx
18
- from ..import_utils import cached_import
217
+ from ..types import RemeshMeta
218
+ from ..utils import cached_import, edge_version_update, kahan_sum_nd
219
+
220
+ CommunityGraph: TypeAlias = Any
221
+ NetworkxModule: TypeAlias = ModuleType
222
+ CommunityModule: TypeAlias = ModuleType
223
+ RemeshEdge: TypeAlias = tuple[Hashable, Hashable]
224
+ NetworkxModules: TypeAlias = tuple[NetworkxModule, CommunityModule]
225
+ RemeshConfigValue: TypeAlias = bool | float | int
226
+
227
+
228
+ # ==============================================================================
229
+ # Phase 1: Structural Memory & Pattern Recognition
230
+ # ==============================================================================
231
+
232
+
233
+ @dataclass
234
+ class StructuralIdentity:
235
+ """Persistent fractal identity maintained across reorganizations.
236
+
237
+ Captures the canonical structural "signature" of a pattern that must be
238
+ preserved as it echoes across scales. This implements TNFR's requirement
239
+ that patterns maintain identity through reorganization.
240
+
241
+ **REMESH ↔ SHA Relationship**: According to TNFR theory, SHA (Silence)
242
+ stabilizes latent network memory by reducing νf → 0, which freezes EPI
243
+ via the nodal equation: ∂EPI/∂t = νf · ΔNFR → 0. When REMESH propagates
244
+ patterns across scales, SHA-frozen nodes act as "structural anchors" that
245
+ maintain identity during reorganization.
246
+
247
+ **Usage Pattern**:
248
+ 1. Apply SHA to freeze node: νf → 0, preserves EPI
249
+ 2. Capture identity from frozen state (this class)
250
+ 3. Apply REMESH to propagate pattern across scales
251
+ 4. Validate identity preservation post-reorganization
252
+
253
+ Attributes
254
+ ----------
255
+ epi_signature : float
256
+ Characteristic EPI pattern value (preserved when SHA applied)
257
+ vf_range : tuple[float, float]
258
+ Range of structural frequencies (min, max) in Hz_str
259
+ phase_pattern : float | None
260
+ Characteristic phase pattern in [0, 2π], if applicable
261
+ frozen_by_sha : bool
262
+ Whether this identity was captured from SHA-frozen state (νf ≈ 0)
263
+ lineage : list[str]
264
+ History of transformations preserving this identity
265
+ tolerance : float
266
+ Maximum deviation for identity match (default: 0.1)
267
+
268
+ Notes
269
+ -----
270
+ From TNFR physics (definitions.py::Silence): SHA reduces νf causing
271
+ ∂EPI/∂t → 0 regardless of ΔNFR. This creates "latent memory" - frozen
272
+ structural patterns that REMESH can propagate coherently across scales.
273
+
274
+ **Do NOT reimplement SHA** - use existing Silence operator from
275
+ tnfr.operators.definitions. This class only captures and validates
276
+ identity, it does NOT apply SHA itself.
277
+
278
+ See Also
279
+ --------
280
+ tnfr.operators.definitions.Silence : SHA operator implementation
281
+ SHA_ALGEBRA_PHYSICS.md : SHA as identity operator derivation
282
+ """
283
+
284
+ epi_signature: float
285
+ vf_range: tuple[float, float]
286
+ phase_pattern: float | None = None
287
+ frozen_by_sha: bool = False
288
+ lineage: list[str] = field(default_factory=list)
289
+ tolerance: float = 0.1
290
+
291
+ def matches(self, node_data: Mapping[str, Any], *, tolerance: float | None = None) -> bool:
292
+ """Check if a node maintains this structural identity.
293
+
294
+ Parameters
295
+ ----------
296
+ node_data : Mapping
297
+ Node attributes containing EPI, vf, and optionally phase
298
+ tolerance : float, optional
299
+ Override default tolerance for this check
300
+
301
+ Returns
302
+ -------
303
+ bool
304
+ True if node matches this identity within tolerance
305
+
306
+ Notes
307
+ -----
308
+ If frozen_by_sha=True, vf check is relaxed since SHA-frozen patterns
309
+ have νf ≈ 0 (frozen state) while maintaining identity via EPI.
310
+ """
311
+ tol = tolerance if tolerance is not None else self.tolerance
312
+
313
+ # Check EPI signature match (primary identity criterion)
314
+ node_epi = _as_float(get_attr(node_data, ALIAS_EPI, 0.0))
315
+ if abs(node_epi - self.epi_signature) > tol:
316
+ return False
317
+
318
+ # Check vf range (relaxed if SHA-frozen)
319
+ node_vf = _as_float(get_attr(node_data, ALIAS_VF, 0.0))
320
+ vf_min, vf_max = self.vf_range
321
+
322
+ if self.frozen_by_sha:
323
+ # SHA-frozen: accept low νf (frozen state) OR original range
324
+ # (pattern may be reactivated after SHA)
325
+ if node_vf < 0.05: # Frozen by SHA (νf → 0)
326
+ pass # Accept - this is expected for SHA-frozen patterns
327
+ elif not (vf_min - tol <= node_vf <= vf_max + tol):
328
+ return False
329
+ else:
330
+ # Normal check: must be within range
331
+ if not (vf_min - tol <= node_vf <= vf_max + tol):
332
+ return False
333
+
334
+ # Check phase pattern if specified
335
+ if self.phase_pattern is not None:
336
+ from ..constants.aliases import ALIAS_THETA
337
+ node_phase = _as_float(get_attr(node_data, ALIAS_THETA, None))
338
+ if node_phase is None:
339
+ return False
340
+ # Phase comparison with circular wrap-around
341
+ import math
342
+ phase_diff = abs(node_phase - self.phase_pattern)
343
+ phase_diff = min(phase_diff, 2 * math.pi - phase_diff)
344
+ if phase_diff > tol:
345
+ return False
346
+
347
+ return True
348
+
349
+ def record_transformation(self, operation: str) -> None:
350
+ """Record a structural transformation in this identity's lineage.
351
+
352
+ Parameters
353
+ ----------
354
+ operation : str
355
+ Description of the transformation applied
356
+ """
357
+ self.lineage.append(operation)
358
+
359
+ @classmethod
360
+ def capture_from_node(
361
+ cls,
362
+ node_data: Mapping[str, Any],
363
+ *,
364
+ is_sha_frozen: bool = False,
365
+ tolerance: float = 0.1,
366
+ ) -> "StructuralIdentity":
367
+ """Capture structural identity from a node's current state.
368
+
369
+ This creates a "memory snapshot" of the node's structural signature
370
+ that can be propagated via REMESH across scales.
371
+
372
+ Parameters
373
+ ----------
374
+ node_data : Mapping
375
+ Node attributes containing EPI, vf, phase
376
+ is_sha_frozen : bool, default=False
377
+ If True, mark identity as captured from SHA-frozen state (νf ≈ 0).
378
+ **NOTE**: This does NOT apply SHA - it only marks that SHA was
379
+ already applied. Use tnfr.operators.definitions.Silence to apply SHA.
380
+ tolerance : float, default=0.1
381
+ Tolerance for future identity matching
382
+
383
+ Returns
384
+ -------
385
+ StructuralIdentity
386
+ Captured structural identity
387
+
388
+ Notes
389
+ -----
390
+ **REMESH ↔ SHA Integration Pattern**:
391
+
392
+ 1. Apply SHA operator to freeze node (see tnfr.operators.definitions.Silence)
393
+ 2. Call this method with is_sha_frozen=True to capture frozen state
394
+ 3. Apply REMESH to propagate pattern across scales
395
+ 4. Use identity.matches() to validate preservation
396
+
397
+ **DO NOT use this to apply SHA** - SHA is a separate operator that must
398
+ be applied via the grammar system. This only captures state AFTER SHA.
399
+
400
+ Example
401
+ -------
402
+ >>> from tnfr.operators.definitions import Silence
403
+ >>> from tnfr.structural import run_sequence
404
+ >>> # Step 1: Apply SHA operator to freeze node
405
+ >>> run_sequence(G, node, [Silence()])
406
+ >>> # Step 2: Capture frozen identity
407
+ >>> identity = StructuralIdentity.capture_from_node(
408
+ ... G.nodes[node],
409
+ ... is_sha_frozen=True
410
+ ... )
411
+ >>> # Step 3: Apply REMESH (propagates frozen pattern)
412
+ >>> # ... REMESH operations ...
413
+ >>> # Step 4: Validate identity preserved
414
+ >>> assert identity.matches(G.nodes[node])
415
+ """
416
+ epi = _as_float(get_attr(node_data, ALIAS_EPI, 0.0))
417
+ vf = _as_float(get_attr(node_data, ALIAS_VF, 0.0))
418
+
419
+ # For vf_range, use small window around current value
420
+ # (unless SHA-frozen, in which case we expect νf ≈ 0)
421
+ vf_tolerance = 0.1
422
+ vf_min = max(0.0, vf - vf_tolerance)
423
+ vf_max = vf + vf_tolerance
424
+
425
+ # Capture phase if present
426
+ from ..constants.aliases import ALIAS_THETA
427
+ phase = _as_float(get_attr(node_data, ALIAS_THETA, None))
428
+
429
+ identity = cls(
430
+ epi_signature=epi,
431
+ vf_range=(vf_min, vf_max),
432
+ phase_pattern=phase if phase is not None else None,
433
+ frozen_by_sha=is_sha_frozen,
434
+ tolerance=tolerance,
435
+ )
436
+
437
+ if is_sha_frozen:
438
+ identity.record_transformation(
439
+ "Captured from SHA-frozen state (latent structural memory)"
440
+ )
441
+
442
+ return identity
443
+
444
+
445
+ def structural_similarity(
446
+ epi1: float | Sequence[float],
447
+ epi2: float | Sequence[float],
448
+ *,
449
+ metric: str = "euclidean",
450
+ ) -> float:
451
+ """Compute structural similarity between two EPI patterns.
452
+
453
+ Implements pattern matching for structural memory recognition.
454
+ Returns a similarity score in [0, 1] where 1 = identical patterns.
455
+
456
+ Parameters
457
+ ----------
458
+ epi1, epi2 : float or array-like
459
+ EPI patterns to compare. Can be scalars or vectors.
460
+ metric : {'euclidean', 'cosine', 'correlation'}
461
+ Distance/similarity metric to use
462
+
463
+ Returns
464
+ -------
465
+ float
466
+ Similarity score in [0, 1], where 1 indicates identical patterns
467
+
468
+ Notes
469
+ -----
470
+ This function is fundamental to REMESH's structural memory capability,
471
+ enabling pattern recognition across network scales.
472
+
473
+ Examples
474
+ --------
475
+ >>> structural_similarity(0.5, 0.52) # Nearly identical scalars
476
+ 0.98
477
+ >>> structural_similarity([0.5, 0.3], [0.52, 0.31]) # Similar vectors
478
+ 0.97
479
+ """
480
+ # Convert to numpy arrays for consistent handling
481
+ np = _get_numpy()
482
+ if np is None:
483
+ # Fallback: scalar comparison only
484
+ if isinstance(epi1, (list, tuple)) or isinstance(epi2, (list, tuple)):
485
+ raise ImportError(
486
+ "NumPy required for vector EPI comparison. "
487
+ "Install numpy: pip install numpy"
488
+ )
489
+ # Simple scalar distance -> similarity
490
+ distance = abs(float(epi1) - float(epi2))
491
+ # Map distance to similarity using exponential decay
492
+ # similarity = exp(-k * distance) where k controls sensitivity
493
+ import math
494
+ k = 5.0 # Sensitivity parameter (higher = stricter matching)
495
+ return math.exp(-k * distance)
496
+
497
+ # NumPy available - use vector operations
498
+ arr1 = np.atleast_1d(np.asarray(epi1, dtype=float))
499
+ arr2 = np.atleast_1d(np.asarray(epi2, dtype=float))
500
+
501
+ if arr1.shape != arr2.shape:
502
+ raise ValueError(
503
+ f"EPI patterns must have same shape: {arr1.shape} vs {arr2.shape}"
504
+ )
505
+
506
+ if metric == "euclidean":
507
+ distance = np.linalg.norm(arr1 - arr2)
508
+ # Normalize by dimension for fair comparison across scales
509
+ distance /= np.sqrt(len(arr1))
510
+ # Convert to similarity
511
+ import math
512
+ k = 5.0
513
+ return float(math.exp(-k * distance))
514
+
515
+ elif metric == "cosine":
516
+ # Cosine similarity: (a · b) / (||a|| ||b||)
517
+ dot_product = np.dot(arr1, arr2)
518
+ norm1 = np.linalg.norm(arr1)
519
+ norm2 = np.linalg.norm(arr2)
520
+ if norm1 < 1e-10 or norm2 < 1e-10:
521
+ return 0.0 # Zero vectors have no meaningful similarity
522
+ similarity = dot_product / (norm1 * norm2)
523
+ # Map [-1, 1] to [0, 1]
524
+ return float((similarity + 1.0) / 2.0)
525
+
526
+ elif metric == "correlation":
527
+ # Pearson correlation coefficient
528
+ if len(arr1) < 2:
529
+ # Fall back to euclidean for scalars
530
+ return structural_similarity(epi1, epi2, metric="euclidean")
531
+ corr_matrix = np.corrcoef(arr1, arr2)
532
+ correlation = corr_matrix[0, 1]
533
+ # Handle NaN (constant arrays)
534
+ if not np.isfinite(correlation):
535
+ correlation = 1.0 if np.allclose(arr1, arr2) else 0.0
536
+ # Map [-1, 1] to [0, 1]
537
+ return float((correlation + 1.0) / 2.0)
538
+
539
+ else:
540
+ raise ValueError(
541
+ f"Unknown metric '{metric}'. Choose from: euclidean, cosine, correlation"
542
+ )
543
+
544
+
545
+ def structural_memory_match(
546
+ G: CommunityGraph,
547
+ source_node: Hashable,
548
+ target_nodes: Iterable[Hashable] | None = None,
549
+ *,
550
+ threshold: float = 0.75,
551
+ metric: str = "euclidean",
552
+ ) -> list[tuple[Hashable, float]]:
553
+ """Identify nodes with EPI patterns similar to source node.
554
+
555
+ Implements REMESH's structural memory: recognizing coherent patterns
556
+ across the network that resonate with the source pattern.
557
+
558
+ Parameters
559
+ ----------
560
+ G : TNFRGraph
561
+ Network containing nodes with EPI attributes
562
+ source_node : Hashable
563
+ Node whose pattern to match against
564
+ target_nodes : Iterable, optional
565
+ Nodes to search. If None, searches all nodes except source.
566
+ threshold : float, default=0.75
567
+ Minimum similarity score for a match
568
+ metric : str, default='euclidean'
569
+ Similarity metric (see structural_similarity)
570
+
571
+ Returns
572
+ -------
573
+ list of (node, similarity) tuples
574
+ Nodes matching the source pattern, sorted by similarity (highest first)
575
+
576
+ Notes
577
+ -----
578
+ This function is critical for REMESH's ability to propagate patterns
579
+ coherently across scales, as specified in TNFR theoretical foundation.
580
+ """
581
+ source_epi = _as_float(get_attr(G.nodes[source_node], ALIAS_EPI, 0.0))
582
+
583
+ if target_nodes is None:
584
+ target_nodes = [n for n in G.nodes() if n != source_node]
585
+
586
+ matches = []
587
+ for target in target_nodes:
588
+ if target == source_node:
589
+ continue
590
+ target_epi = _as_float(get_attr(G.nodes[target], ALIAS_EPI, 0.0))
591
+ similarity = structural_similarity(source_epi, target_epi, metric=metric)
592
+ if similarity >= threshold:
593
+ matches.append((target, similarity))
594
+
595
+ # Sort by similarity descending
596
+ matches.sort(key=lambda x: x[1], reverse=True)
597
+ return matches
19
598
 
20
- ALIAS_EPI = get_aliases("EPI")
599
+
600
+ def compute_structural_signature(
601
+ G: CommunityGraph,
602
+ node: Hashable,
603
+ ) -> Any:
604
+ """Compute multidimensional structural signature of a node.
605
+
606
+ The signature captures the node's complete structural identity including:
607
+ - EPI: coherence magnitude
608
+ - νf: structural frequency
609
+ - θ: phase
610
+ - ΔNFR: reorganization gradient
611
+ - Topological properties: degree, local clustering
612
+
613
+ This signature enables REMESH's structural memory capability - recognizing
614
+ the same pattern across different nodes and scales.
615
+
616
+ Parameters
617
+ ----------
618
+ G : TNFRGraph
619
+ Network containing the node
620
+ node : Hashable
621
+ Node identifier whose signature to compute
622
+
623
+ Returns
624
+ -------
625
+ signature : ndarray, shape (n_features,)
626
+ Normalized structural signature vector. If NumPy unavailable, returns
627
+ a tuple of features.
628
+
629
+ Notes
630
+ -----
631
+ The signature is normalized to unit length for consistent similarity
632
+ comparisons across different network scales and configurations.
633
+
634
+ Features included:
635
+ 1. EPI magnitude (coherence)
636
+ 2. νf (structural frequency in Hz_str)
637
+ 3. sin(θ), cos(θ) (circular phase representation)
638
+ 4. ΔNFR (reorganization gradient)
639
+ 5. Normalized degree (relative connectivity)
640
+ 6. Local clustering coefficient (triadic closure)
641
+
642
+ Examples
643
+ --------
644
+ >>> G = nx.Graph()
645
+ >>> G.add_node(1, EPI=0.5, vf=1.0, theta=0.2, DNFR=0.1)
646
+ >>> G.add_edge(1, 2)
647
+ >>> sig = compute_structural_signature(G, 1)
648
+ >>> len(sig) # 7 features
649
+ 7
650
+ """
651
+ # Extract TNFR structural attributes
652
+ epi = _as_float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
653
+ vf = _as_float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
654
+ from ..constants.aliases import ALIAS_THETA
655
+ theta = _as_float(get_attr(G.nodes[node], ALIAS_THETA, 0.0))
656
+ dnfr = _as_float(get_attr(G.nodes[node], ALIAS_DNFR, 0.0))
657
+
658
+ # Compute topological features
659
+ degree = G.degree(node) if G.has_node(node) else 0
660
+ n_nodes = G.number_of_nodes()
661
+ normalized_degree = degree / n_nodes if n_nodes > 0 else 0.0
662
+
663
+ # Local clustering coefficient (requires networkx)
664
+ try:
665
+ nx, _ = _get_networkx_modules()
666
+ clustering = nx.clustering(G, node)
667
+ except Exception:
668
+ clustering = 0.0
669
+
670
+ # Build feature vector
671
+ import math
672
+ features = [
673
+ epi,
674
+ vf,
675
+ math.sin(theta), # Circular phase representation
676
+ math.cos(theta),
677
+ dnfr,
678
+ normalized_degree,
679
+ clustering,
680
+ ]
681
+
682
+ # Try to use NumPy for normalization
683
+ np = _get_numpy()
684
+ if np is not None:
685
+ signature = np.array(features, dtype=float)
686
+ norm = np.linalg.norm(signature)
687
+ if norm > 1e-10:
688
+ signature = signature / norm
689
+ return signature
690
+ else:
691
+ # Fallback: manual normalization
692
+ norm = math.sqrt(sum(f * f for f in features))
693
+ if norm > 1e-10:
694
+ features = [f / norm for f in features]
695
+ return tuple(features)
696
+
697
+
698
+ def detect_recursive_patterns(
699
+ G: CommunityGraph,
700
+ threshold: float = 0.75,
701
+ metric: str = "cosine",
702
+ min_cluster_size: int = 2,
703
+ ) -> list[list[Hashable]]:
704
+ """Detect groups of nodes with similar structural patterns.
705
+
706
+ These groups represent the same EPI pattern replicated across different
707
+ nodes/scales in the network. This is fundamental to REMESH's capability
708
+ to recognize and propagate structural identity.
709
+
710
+ Parameters
711
+ ----------
712
+ G : TNFRGraph
713
+ Network to analyze
714
+ threshold : float, default=0.75
715
+ Minimum similarity score (0-1) to consider patterns as "same identity".
716
+ Higher values require stricter matching.
717
+ metric : str, default='cosine'
718
+ Similarity metric for signature comparison.
719
+ Options: 'cosine', 'euclidean', 'correlation'
720
+ min_cluster_size : int, default=2
721
+ Minimum number of nodes required to consider as a recursive pattern.
722
+ Single nodes are not patterns.
723
+
724
+ Returns
725
+ -------
726
+ clusters : list of list
727
+ Each cluster contains nodes sharing a structural pattern.
728
+ Clusters are independent - nodes appear in at most one cluster.
729
+
730
+ Notes
731
+ -----
732
+ Algorithm uses greedy clustering:
733
+ 1. Compute structural signatures for all nodes
734
+ 2. For each unvisited node, find all similar nodes (similarity >= threshold)
735
+ 3. Form cluster if size >= min_cluster_size
736
+ 4. Mark all cluster members as visited
737
+
738
+ This implements TNFR's principle: "A node can recognize itself in other
739
+ nodes" through structural resonance.
740
+
741
+ Examples
742
+ --------
743
+ >>> # Network with two groups of similar nodes
744
+ >>> clusters = detect_recursive_patterns(G, threshold=0.8)
745
+ >>> len(clusters)
746
+ 2
747
+ >>> all(len(c) >= 2 for c in clusters)
748
+ True
749
+ """
750
+ nodes = list(G.nodes())
751
+ n = len(nodes)
752
+
753
+ if n < min_cluster_size:
754
+ return []
755
+
756
+ # Compute all signatures
757
+ signatures = {}
758
+ for node in nodes:
759
+ signatures[node] = compute_structural_signature(G, node)
760
+
761
+ # Compute similarity matrix (only upper triangle needed)
762
+ np = _get_numpy()
763
+ if np is not None:
764
+ # Use NumPy for efficient computation
765
+ sig_array = np.array([signatures[node] for node in nodes])
766
+
767
+ if metric == "cosine":
768
+ # Cosine similarity matrix
769
+ norms = np.linalg.norm(sig_array, axis=1, keepdims=True)
770
+ norms = np.maximum(norms, 1e-10) # Avoid division by zero
771
+ normalized = sig_array / norms
772
+ similarities = np.dot(normalized, normalized.T)
773
+ elif metric == "euclidean":
774
+ # Euclidean distance -> similarity
775
+ from scipy.spatial.distance import pdist, squareform
776
+ distances = squareform(pdist(sig_array, metric='euclidean'))
777
+ max_dist = np.sqrt(2) # Max distance for unit vectors
778
+ similarities = 1.0 - (distances / max_dist)
779
+ elif metric == "correlation":
780
+ # Pearson correlation
781
+ similarities = np.corrcoef(sig_array)
782
+ # Handle NaN (constant signatures)
783
+ similarities = np.nan_to_num(similarities, nan=0.0)
784
+ else:
785
+ raise ValueError(f"Unknown metric: {metric}")
786
+ else:
787
+ # Fallback: pairwise computation
788
+ similarities = {}
789
+ for i, node1 in enumerate(nodes):
790
+ for j, node2 in enumerate(nodes):
791
+ if i == j:
792
+ similarities[(i, j)] = 1.0
793
+ elif i < j:
794
+ # Use existing structural_similarity function
795
+ sim = structural_similarity(
796
+ signatures[node1],
797
+ signatures[node2],
798
+ metric=metric
799
+ )
800
+ similarities[(i, j)] = sim
801
+ similarities[(j, i)] = sim
802
+
803
+ # Greedy clustering
804
+ visited = set()
805
+ clusters = []
806
+
807
+ for i, node in enumerate(nodes):
808
+ if node in visited:
809
+ continue
810
+
811
+ # Find all similar nodes
812
+ cluster = [node]
813
+ visited.add(node)
814
+
815
+ for j, other_node in enumerate(nodes):
816
+ if other_node in visited:
817
+ continue
818
+
819
+ # Get similarity
820
+ if np is not None:
821
+ sim = similarities[i, j]
822
+ else:
823
+ sim = similarities.get((i, j), 0.0)
824
+
825
+ if sim >= threshold:
826
+ cluster.append(other_node)
827
+ visited.add(other_node)
828
+
829
+ # Only keep clusters meeting minimum size
830
+ if len(cluster) >= min_cluster_size:
831
+ clusters.append(cluster)
832
+
833
+ return clusters
834
+
835
+
836
+ def identify_pattern_origin(
837
+ G: CommunityGraph,
838
+ cluster: Sequence[Hashable],
839
+ ) -> Hashable | None:
840
+ """Identify the origin node of a recursive structural pattern.
841
+
842
+ The origin is the node with the strongest structural manifestation,
843
+ determined by combining coherence (EPI) and reorganization capacity (νf).
844
+ This represents the "source" from which the pattern can most coherently
845
+ propagate.
846
+
847
+ Parameters
848
+ ----------
849
+ G : TNFRGraph
850
+ Network containing the cluster
851
+ cluster : sequence of node identifiers
852
+ Nodes sharing the same structural pattern
853
+
854
+ Returns
855
+ -------
856
+ origin_node : Hashable or None
857
+ Node identified as pattern origin (highest structural strength).
858
+ Returns None if cluster is empty.
859
+
860
+ Notes
861
+ -----
862
+ Structural strength score = EPI × νf
863
+
864
+ This metric captures:
865
+ - EPI: How coherent the pattern is (magnitude)
866
+ - νf: How actively the pattern reorganizes (frequency)
867
+
868
+ High score indicates a node that both maintains strong coherence AND
869
+ has high reorganization capacity, making it ideal for propagation.
870
+
871
+ Physical interpretation: The origin node is the "loudest" instance of
872
+ the pattern in the network's structural field.
873
+
874
+ Examples
875
+ --------
876
+ >>> cluster = [1, 2, 3] # Nodes with similar patterns
877
+ >>> origin = identify_pattern_origin(G, cluster)
878
+ >>> # Origin will be node with highest EPI × νf
879
+ """
880
+ if not cluster:
881
+ return None
882
+
883
+ # Compute structural strength for each node
884
+ scores = []
885
+ for node in cluster:
886
+ epi = _as_float(get_attr(G.nodes[node], ALIAS_EPI, 0.0))
887
+ vf = _as_float(get_attr(G.nodes[node], ALIAS_VF, 0.0))
888
+ # Structural strength = coherence × reorganization capacity
889
+ strength = epi * vf
890
+ scores.append((strength, node))
891
+
892
+ # Return node with maximum strength
893
+ scores.sort(reverse=True)
894
+ return scores[0][1]
895
+
896
+
897
+ def propagate_structural_identity(
898
+ G: CommunityGraph,
899
+ origin_node: Hashable,
900
+ target_nodes: Sequence[Hashable],
901
+ propagation_strength: float = 0.5,
902
+ ) -> None:
903
+ """Propagate structural identity from origin to similar nodes.
904
+
905
+ This implements REMESH's core principle: nodes with similar patterns
906
+ mutually reinforce their coherence through structural resonance.
907
+ The origin's pattern is propagated to targets via weighted interpolation.
908
+
909
+ Parameters
910
+ ----------
911
+ G : TNFRGraph
912
+ Network to modify (changes node attributes in-place)
913
+ origin_node : Hashable
914
+ Source node whose pattern to propagate
915
+ target_nodes : sequence
916
+ Nodes that will receive pattern reinforcement
917
+ propagation_strength : float, default=0.5
918
+ Interpolation weight in [0, 1]:
919
+ - 0.0: No effect (targets unchanged)
920
+ - 1.0: Complete copy (targets become identical to origin)
921
+ - 0.5: Balanced blending
922
+
923
+ Notes
924
+ -----
925
+ For each target node, updates:
926
+ - EPI: new = (1-α)×old + α×origin
927
+ - νf: new = (1-α)×old + α×origin
928
+ - θ: new = (1-α)×old + α×origin
929
+
930
+ Where α = propagation_strength.
931
+
932
+ Updates respect structural boundaries (EPI_MIN, EPI_MAX) via
933
+ structural_clip to prevent overflow and maintain physical validity.
934
+
935
+ Records propagation in node's 'structural_lineage' attribute for
936
+ traceability and analysis.
937
+
938
+ **TNFR Physics**: This preserves the nodal equation ∂EPI/∂t = νf·ΔNFR
939
+ by interpolating the structural state rather than imposing it directly.
940
+
941
+ Examples
942
+ --------
943
+ >>> origin = 1
944
+ >>> targets = [2, 3, 4] # Similar nodes
945
+ >>> propagate_structural_identity(G, origin, targets, 0.3)
946
+ >>> # Targets now have patterns 30% closer to origin
947
+ """
948
+ # Get origin pattern
949
+ origin_epi = _as_float(get_attr(G.nodes[origin_node], ALIAS_EPI, 0.0))
950
+ origin_vf = _as_float(get_attr(G.nodes[origin_node], ALIAS_VF, 0.0))
951
+ from ..constants.aliases import ALIAS_THETA
952
+ origin_theta = _as_float(get_attr(G.nodes[origin_node], ALIAS_THETA, 0.0))
953
+
954
+ # Get structural bounds
955
+ from ..constants import DEFAULTS
956
+ epi_min = float(G.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
957
+ epi_max = float(G.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))
958
+
959
+ # Get clip mode
960
+ clip_mode_str = str(G.graph.get("CLIP_MODE", "hard"))
961
+ if clip_mode_str not in ("hard", "soft"):
962
+ clip_mode_str = "hard"
963
+
964
+ # Propagate to each target
965
+ for target in target_nodes:
966
+ if target == origin_node:
967
+ continue # Don't propagate to self
968
+
969
+ # Get current target state
970
+ target_epi = _as_float(get_attr(G.nodes[target], ALIAS_EPI, 0.0))
971
+ target_vf = _as_float(get_attr(G.nodes[target], ALIAS_VF, 0.0))
972
+ target_theta = _as_float(get_attr(G.nodes[target], ALIAS_THETA, 0.0))
973
+
974
+ # Interpolate toward origin pattern
975
+ new_epi = (1.0 - propagation_strength) * target_epi + propagation_strength * origin_epi
976
+ new_vf = (1.0 - propagation_strength) * target_vf + propagation_strength * origin_vf
977
+ new_theta = (1.0 - propagation_strength) * target_theta + propagation_strength * origin_theta
978
+
979
+ # Apply structural clipping to preserve boundaries
980
+ from ..dynamics.structural_clip import structural_clip
981
+ new_epi = structural_clip(new_epi, lo=epi_min, hi=epi_max, mode=clip_mode_str) # type: ignore[arg-type]
982
+
983
+ # Update node attributes
984
+ set_attr(G.nodes[target], ALIAS_EPI, new_epi)
985
+ set_attr(G.nodes[target], ALIAS_VF, new_vf)
986
+ set_attr(G.nodes[target], ALIAS_THETA, new_theta)
987
+
988
+ # Record lineage for traceability
989
+ if 'structural_lineage' not in G.nodes[target]:
990
+ G.nodes[target]['structural_lineage'] = []
991
+
992
+ # Get current step for timestamp
993
+ from ..glyph_history import current_step_idx
994
+ step = current_step_idx(G)
995
+
996
+ G.nodes[target]['structural_lineage'].append({
997
+ 'origin': origin_node,
998
+ 'step': step,
999
+ 'propagation_strength': propagation_strength,
1000
+ 'epi_before': target_epi,
1001
+ 'epi_after': new_epi,
1002
+ })
1003
+
1004
+
1005
+ @cache
1006
+ def _get_numpy() -> ModuleType | None:
1007
+ """Get numpy module if available, None otherwise."""
1008
+ return cached_import("numpy")
1009
+
1010
+
1011
+ # ==============================================================================
1012
+ # Phase 2: Coherence Preservation & Fidelity Validation
1013
+ # ==============================================================================
1014
+
1015
+
1016
+ class RemeshCoherenceLossError(Exception):
1017
+ """Raised when REMESH reorganization loses structural coherence.
1018
+
1019
+ REMESH must preserve coherence during reorganization. This error indicates
1020
+ that the structural fidelity dropped below acceptable thresholds, violating
1021
+ TNFR's requirement that "coherence propagates structurally, not imposed."
1022
+ """
1023
+
1024
+ def __init__(self, fidelity: float, min_fidelity: float, details: dict[str, Any] | None = None):
1025
+ """Initialize coherence loss error.
1026
+
1027
+ Parameters
1028
+ ----------
1029
+ fidelity : float
1030
+ Measured structural fidelity (coherence_after / coherence_before)
1031
+ min_fidelity : float
1032
+ Minimum required fidelity threshold
1033
+ details : dict, optional
1034
+ Additional diagnostic information
1035
+ """
1036
+ self.fidelity = fidelity
1037
+ self.min_fidelity = min_fidelity
1038
+ self.details = details or {}
1039
+
1040
+ super().__init__(
1041
+ f"REMESH coherence loss: structural fidelity {fidelity:.2%} "
1042
+ f"< minimum {min_fidelity:.2%}\n"
1043
+ f" Details: {details}"
1044
+ )
1045
+
1046
+
1047
+ def validate_coherence_preservation(
1048
+ G_before: CommunityGraph,
1049
+ G_after: CommunityGraph,
1050
+ *,
1051
+ min_fidelity: float = 0.85,
1052
+ rollback_on_failure: bool = False,
1053
+ ) -> float:
1054
+ """Validate that reorganization preserved structural coherence.
1055
+
1056
+ Implements TNFR requirement that REMESH reorganization must occur
1057
+ "without loss of coherence" - the total structural stability must
1058
+ be maintained within acceptable bounds.
1059
+
1060
+ This function uses the canonical TNFR coherence computation from
1061
+ tnfr.metrics.common.compute_coherence() which is based on ΔNFR and dEPI.
1062
+
1063
+ Parameters
1064
+ ----------
1065
+ G_before : TNFRGraph
1066
+ Network state before reorganization
1067
+ G_after : TNFRGraph
1068
+ Network state after reorganization
1069
+ min_fidelity : float, default=0.85
1070
+ Minimum acceptable fidelity (coherence_after / coherence_before)
1071
+ rollback_on_failure : bool, default=False
1072
+ If True and fidelity check fails, raise RemeshCoherenceLossError
1073
+
1074
+ Returns
1075
+ -------
1076
+ float
1077
+ Structural fidelity score (coherence_after / coherence_before)
1078
+
1079
+ Raises
1080
+ ------
1081
+ RemeshCoherenceLossError
1082
+ If fidelity < min_fidelity and rollback_on_failure=True
1083
+
1084
+ Notes
1085
+ -----
1086
+ Structural fidelity ≈ 1.0 indicates perfect coherence preservation.
1087
+ Fidelity > 1.0 is possible (reorganization increased coherence).
1088
+ Fidelity < min_fidelity indicates unacceptable coherence loss.
1089
+
1090
+ Uses canonical TNFR coherence: C = 1/(1 + |ΔNFR|_mean + |dEPI|_mean)
1091
+ from tnfr.metrics.common module.
1092
+ """
1093
+ # Use canonical TNFR coherence computation
1094
+ from ..metrics.common import compute_coherence
1095
+
1096
+ coherence_before = compute_coherence(G_before)
1097
+ coherence_after = compute_coherence(G_after)
1098
+
1099
+ if coherence_before < 1e-10:
1100
+ # Edge case: network had no coherence to begin with
1101
+ return 1.0
1102
+
1103
+ structural_fidelity = coherence_after / coherence_before
1104
+
1105
+ if rollback_on_failure and structural_fidelity < min_fidelity:
1106
+ details = {
1107
+ "coherence_before": coherence_before,
1108
+ "coherence_after": coherence_after,
1109
+ "n_nodes_before": G_before.number_of_nodes(),
1110
+ "n_nodes_after": G_after.number_of_nodes(),
1111
+ }
1112
+ raise RemeshCoherenceLossError(structural_fidelity, min_fidelity, details)
1113
+
1114
+ return structural_fidelity
1115
+
1116
+
1117
+ # ==============================================================================
1118
+ # Original Helper Functions
1119
+ # ==============================================================================
1120
+
1121
+
1122
+ def _as_float(value: Any, default: float = 0.0) -> float:
1123
+ """Best-effort conversion to ``float`` returning ``default`` on failure."""
1124
+
1125
+ if value is None:
1126
+ return default
1127
+ try:
1128
+ return float(value)
1129
+ except (TypeError, ValueError):
1130
+ return default
1131
+
1132
+
1133
+ def _ordered_edge(u: Hashable, v: Hashable) -> RemeshEdge:
1134
+ """Return a deterministic ordering for an undirected edge."""
1135
+
1136
+ return (u, v) if repr(u) <= repr(v) else (v, u)
1137
+
1138
+
1139
+ COOLDOWN_KEY = "REMESH_COOLDOWN_WINDOW"
21
1140
 
22
1141
 
23
1142
  @cache
24
- def _get_networkx_modules():
1143
+ def _get_networkx_modules() -> NetworkxModules:
25
1144
  nx = cached_import("networkx")
26
1145
  if nx is None:
27
1146
  raise ImportError(
@@ -34,68 +1153,71 @@ def _get_networkx_modules():
34
1153
  "networkx.algorithms.community is required for community-based "
35
1154
  "operations; install 'networkx' to enable this feature"
36
1155
  )
37
- return nx, nx_comm
1156
+ return cast(NetworkxModule, nx), cast(CommunityModule, nx_comm)
38
1157
 
39
1158
 
40
- def _remesh_alpha_info(G):
1159
+ def _remesh_alpha_info(G: CommunityGraph) -> tuple[float, str]:
41
1160
  """Return ``(alpha, source)`` with explicit precedence."""
42
- if bool(
43
- G.graph.get("REMESH_ALPHA_HARD", REMESH_DEFAULTS["REMESH_ALPHA_HARD"])
44
- ):
45
- val = float(
46
- G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"])
1161
+ if bool(G.graph.get("REMESH_ALPHA_HARD", REMESH_DEFAULTS["REMESH_ALPHA_HARD"])):
1162
+ val = _as_float(
1163
+ G.graph.get("REMESH_ALPHA", REMESH_DEFAULTS["REMESH_ALPHA"]),
1164
+ float(REMESH_DEFAULTS["REMESH_ALPHA"]),
47
1165
  )
48
1166
  return val, "REMESH_ALPHA"
49
1167
  gf = G.graph.get("GLYPH_FACTORS", DEFAULTS.get("GLYPH_FACTORS", {}))
50
1168
  if "REMESH_alpha" in gf:
51
- return float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
1169
+ return _as_float(gf["REMESH_alpha"]), "GLYPH_FACTORS.REMESH_alpha"
52
1170
  if "REMESH_ALPHA" in G.graph:
53
- return float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
1171
+ return _as_float(G.graph["REMESH_ALPHA"]), "REMESH_ALPHA"
54
1172
  return (
55
1173
  float(REMESH_DEFAULTS["REMESH_ALPHA"]),
56
1174
  "REMESH_DEFAULTS.REMESH_ALPHA",
57
1175
  )
58
1176
 
59
1177
 
60
- def _snapshot_topology(G, nx):
1178
+ def _snapshot_topology(G: CommunityGraph, nx: NetworkxModule) -> str | None:
61
1179
  """Return a hash representing the current graph topology."""
62
1180
  try:
63
1181
  n_nodes = G.number_of_nodes()
64
1182
  n_edges = G.number_of_edges()
65
1183
  degs = sorted(d for _, d in G.degree())
66
1184
  topo_str = f"n={n_nodes};m={n_edges};deg=" + ",".join(map(str, degs))
67
- return hashlib.sha1(topo_str.encode()).hexdigest()[:12]
1185
+ return hashlib.blake2b(topo_str.encode(), digest_size=6).hexdigest()
68
1186
  except (AttributeError, TypeError, nx.NetworkXError):
69
1187
  return None
70
1188
 
71
1189
 
72
- def _snapshot_epi(G):
1190
+ def _snapshot_epi(G: CommunityGraph) -> tuple[float, str]:
73
1191
  """Return ``(mean, checksum)`` of the node EPI values."""
74
1192
  buf = StringIO()
75
1193
  values = []
76
1194
  for n, data in G.nodes(data=True):
77
- v = float(get_attr(data, ALIAS_EPI, 0.0))
1195
+ v = _as_float(get_attr(data, ALIAS_EPI, 0.0))
78
1196
  values.append(v)
79
1197
  buf.write(f"{str(n)}:{round(v, 6)};")
80
1198
  total = kahan_sum_nd(((v,) for v in values), dims=1)[0]
81
1199
  mean_val = total / len(values) if values else 0.0
82
- checksum = hashlib.sha1(buf.getvalue().encode()).hexdigest()[:12]
1200
+ checksum = hashlib.blake2b(buf.getvalue().encode(), digest_size=6).hexdigest()
83
1201
  return float(mean_val), checksum
84
1202
 
85
1203
 
86
- def _log_remesh_event(G, meta):
1204
+ def _log_remesh_event(G: CommunityGraph, meta: RemeshMeta) -> None:
87
1205
  """Store remesh metadata and optionally log and trigger callbacks."""
1206
+ from ..utils import CallbackEvent, callback_manager
1207
+ from ..glyph_history import append_metric
1208
+
88
1209
  G.graph["_REMESH_META"] = meta
89
1210
  if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
90
1211
  hist = G.graph.setdefault("history", {})
91
1212
  append_metric(hist, "remesh_events", dict(meta))
92
- callback_manager.invoke_callbacks(
93
- G, CallbackEvent.ON_REMESH.value, dict(meta)
94
- )
1213
+ callback_manager.invoke_callbacks(G, CallbackEvent.ON_REMESH.value, dict(meta))
95
1214
 
96
1215
 
97
- def apply_network_remesh(G) -> None:
1216
+ def apply_network_remesh(G: CommunityGraph) -> None:
98
1217
  """Network-scale REMESH using ``_epi_hist`` with multi-scale memory."""
1218
+ from ..glyph_history import current_step_idx, ensure_history
1219
+ from ..dynamics.structural_clip import structural_clip
1220
+
99
1221
  nx, _ = _get_networkx_modules()
100
1222
  tau_g = int(get_param(G, "REMESH_TAU_GLOBAL"))
101
1223
  tau_l = int(get_param(G, "REMESH_TAU_LOCAL"))
@@ -112,18 +1234,33 @@ def apply_network_remesh(G) -> None:
112
1234
  topo_hash = _snapshot_topology(G, nx)
113
1235
  epi_mean_before, epi_checksum_before = _snapshot_epi(G)
114
1236
 
1237
+ # Get EPI bounds for structural preservation
1238
+ epi_min = float(G.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
1239
+ epi_max = float(G.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))
1240
+ clip_mode_str = str(G.graph.get("CLIP_MODE", "hard"))
1241
+ if clip_mode_str not in ("hard", "soft"):
1242
+ clip_mode_str = "hard"
1243
+ clip_mode = clip_mode_str # type: ignore[assignment]
1244
+
115
1245
  for n, nd in G.nodes(data=True):
116
- epi_now = get_attr(nd, ALIAS_EPI, 0.0)
117
- epi_old_l = float(past_l.get(n, epi_now))
118
- epi_old_g = float(past_g.get(n, epi_now))
1246
+ epi_now = _as_float(get_attr(nd, ALIAS_EPI, 0.0))
1247
+ epi_old_l = _as_float(
1248
+ past_l.get(n) if isinstance(past_l, Mapping) else None, epi_now
1249
+ )
1250
+ epi_old_g = _as_float(
1251
+ past_g.get(n) if isinstance(past_g, Mapping) else None, epi_now
1252
+ )
119
1253
  mixed = (1 - alpha) * epi_now + alpha * epi_old_l
120
1254
  mixed = (1 - alpha) * mixed + alpha * epi_old_g
121
- set_attr(nd, ALIAS_EPI, mixed)
1255
+
1256
+ # Apply structural boundary preservation to prevent overflow
1257
+ mixed_clipped = structural_clip(mixed, lo=epi_min, hi=epi_max, mode=clip_mode)
1258
+ set_attr(nd, ALIAS_EPI, mixed_clipped)
122
1259
 
123
1260
  epi_mean_after, epi_checksum_after = _snapshot_epi(G)
124
1261
 
125
1262
  step_idx = current_step_idx(G)
126
- meta = {
1263
+ meta: RemeshMeta = {
127
1264
  "alpha": alpha,
128
1265
  "alpha_source": alpha_src,
129
1266
  "tau_global": tau_g,
@@ -148,20 +1285,178 @@ def apply_network_remesh(G) -> None:
148
1285
  _log_remesh_event(G, meta)
149
1286
 
150
1287
 
151
- def _mst_edges_from_epi(nx, nodes, epi):
1288
+ def apply_network_remesh_with_memory(
1289
+ G: CommunityGraph,
1290
+ *,
1291
+ enable_structural_memory: bool = True,
1292
+ similarity_threshold: float = 0.75,
1293
+ similarity_metric: str = "cosine",
1294
+ propagation_strength: float = 0.5,
1295
+ min_cluster_size: int = 2,
1296
+ ) -> None:
1297
+ """Apply REMESH with structural field memory activation.
1298
+
1299
+ This extended version of REMESH implements the theoretical capability
1300
+ of "structural memory": nodes can recognize themselves in other nodes
1301
+ through pattern similarity, enabling coherent propagation across scales.
1302
+
1303
+ The function performs:
1304
+ 1. Standard REMESH reorganization (temporal EPI mixing)
1305
+ 2. Pattern detection (find groups of structurally similar nodes)
1306
+ 3. Identity propagation (reinforce shared patterns from origin nodes)
1307
+
1308
+ Parameters
1309
+ ----------
1310
+ G : TNFRGraph
1311
+ Network to reorganize (modified in-place)
1312
+ enable_structural_memory : bool, default=True
1313
+ Whether to activate structural memory after standard REMESH.
1314
+ If False, performs only standard REMESH.
1315
+ similarity_threshold : float, default=0.75
1316
+ Minimum similarity [0-1] to recognize patterns as "same identity".
1317
+ Higher = stricter matching. Typical range: 0.7-0.9.
1318
+ similarity_metric : str, default='cosine'
1319
+ Metric for comparing structural signatures.
1320
+ Options: 'cosine', 'euclidean', 'correlation'
1321
+ propagation_strength : float, default=0.5
1322
+ Interpolation weight [0-1] for identity propagation.
1323
+ - 0.0: No propagation (structural memory detection only)
1324
+ - 0.5: Balanced blending (recommended)
1325
+ - 1.0: Full replacement (aggressive, may reduce diversity)
1326
+ min_cluster_size : int, default=2
1327
+ Minimum nodes required to form a recursive pattern.
1328
+ Single isolated nodes are not considered patterns.
1329
+
1330
+ Notes
1331
+ -----
1332
+ **TNFR Physics**: This implements the principle that "coherence propagates
1333
+ structurally, not imposed" (TNFR.pdf § 4.2). Patterns that resonate across
1334
+ the network mutually reinforce through similarity-based coupling.
1335
+
1336
+ **Workflow**:
1337
+ 1. `apply_network_remesh(G)` - Standard temporal memory mixing
1338
+ 2. `detect_recursive_patterns(G, threshold)` - Find similar node groups
1339
+ 3. For each cluster:
1340
+ - `identify_pattern_origin(G, cluster)` - Find strongest instance
1341
+ - `propagate_structural_identity(G, origin, targets)` - Reinforce pattern
1342
+
1343
+ **Telemetry**: Logs structural memory events to G.graph['history']['structural_memory_events']
1344
+ including cluster statistics and propagation metadata.
1345
+
1346
+ **Canonical Relationships**:
1347
+ - Hierarchical REMESH: Combine with IL (coherence) for stable multi-level propagation
1348
+ - Rhizomatic REMESH: Combine with UM (coupling) for decentralized pattern spread
1349
+ - Fractal Harmonic: Combine with RA (resonance) for symmetric amplification
1350
+
1351
+ Examples
1352
+ --------
1353
+ >>> # Standard REMESH with structural memory (recommended)
1354
+ >>> apply_network_remesh_with_memory(G)
1355
+ >>>
1356
+ >>> # Strict pattern matching with gentle propagation
1357
+ >>> apply_network_remesh_with_memory(
1358
+ ... G,
1359
+ ... similarity_threshold=0.85,
1360
+ ... propagation_strength=0.3
1361
+ ... )
1362
+ >>>
1363
+ >>> # Disable structural memory (standard REMESH only)
1364
+ >>> apply_network_remesh_with_memory(G, enable_structural_memory=False)
1365
+ """
1366
+ # Phase 1: Apply standard REMESH (temporal memory)
1367
+ apply_network_remesh(G)
1368
+
1369
+ if not enable_structural_memory:
1370
+ return
1371
+
1372
+ # Phase 2: Structural memory - detect and propagate patterns
1373
+ try:
1374
+ # Detect recursive patterns across network
1375
+ clusters = detect_recursive_patterns(
1376
+ G,
1377
+ threshold=similarity_threshold,
1378
+ metric=similarity_metric,
1379
+ min_cluster_size=min_cluster_size,
1380
+ )
1381
+
1382
+ # Propagate identity from origin to similar nodes
1383
+ propagation_events = []
1384
+ for cluster in clusters:
1385
+ if len(cluster) < min_cluster_size:
1386
+ continue
1387
+
1388
+ # Identify strongest instance of pattern
1389
+ origin = identify_pattern_origin(G, cluster)
1390
+ if origin is None:
1391
+ continue
1392
+
1393
+ # Propagate to other cluster members
1394
+ targets = [n for n in cluster if n != origin]
1395
+ if targets:
1396
+ propagate_structural_identity(
1397
+ G,
1398
+ origin,
1399
+ targets,
1400
+ propagation_strength=propagation_strength,
1401
+ )
1402
+
1403
+ propagation_events.append({
1404
+ 'origin': origin,
1405
+ 'n_targets': len(targets),
1406
+ 'targets': targets[:5], # Sample for telemetry (avoid bloat)
1407
+ })
1408
+
1409
+ # Log structural memory event
1410
+ if G.graph.get("REMESH_LOG_EVENTS", REMESH_DEFAULTS["REMESH_LOG_EVENTS"]):
1411
+ from ..glyph_history import append_metric
1412
+ hist = G.graph.setdefault("history", {})
1413
+ append_metric(
1414
+ hist,
1415
+ "structural_memory_events",
1416
+ {
1417
+ 'n_clusters': len(clusters),
1418
+ 'cluster_sizes': [len(c) for c in clusters],
1419
+ 'n_propagations': len(propagation_events),
1420
+ 'propagation_events': propagation_events[:10], # Sample
1421
+ 'similarity_threshold': similarity_threshold,
1422
+ 'similarity_metric': similarity_metric,
1423
+ 'propagation_strength': propagation_strength,
1424
+ 'min_cluster_size': min_cluster_size,
1425
+ },
1426
+ )
1427
+
1428
+ except Exception as e:
1429
+ # Graceful degradation: if structural memory fails, REMESH still applied
1430
+ import warnings
1431
+ warnings.warn(
1432
+ f"Structural memory activation failed: {e}. "
1433
+ "Standard REMESH applied successfully.",
1434
+ RuntimeWarning,
1435
+ stacklevel=2,
1436
+ )
1437
+
1438
+
1439
+ def _mst_edges_from_epi(
1440
+ nx: NetworkxModule,
1441
+ nodes: Sequence[Hashable],
1442
+ epi: Mapping[Hashable, float],
1443
+ ) -> set[RemeshEdge]:
152
1444
  """Return MST edges based on absolute EPI distance."""
153
1445
  H = nx.Graph()
154
1446
  H.add_nodes_from(nodes)
155
1447
  H.add_weighted_edges_from(
156
1448
  (u, v, abs(epi[u] - epi[v])) for u, v in combinations(nodes, 2)
157
1449
  )
158
- return {
159
- tuple(sorted((u, v)))
160
- for u, v in nx.minimum_spanning_edges(H, data=False)
161
- }
1450
+ return {_ordered_edge(u, v) for u, v in nx.minimum_spanning_edges(H, data=False)}
162
1451
 
163
1452
 
164
- def _knn_edges(nodes, epi, k_val, p_rewire, rnd):
1453
+ def _knn_edges(
1454
+ nodes: Sequence[Hashable],
1455
+ epi: Mapping[Hashable, float],
1456
+ k_val: int,
1457
+ p_rewire: float,
1458
+ rnd: random.Random,
1459
+ ) -> set[RemeshEdge]:
165
1460
  """Edges linking each node to its ``k`` nearest neighbours in EPI."""
166
1461
  new_edges = set()
167
1462
  node_set = set(nodes)
@@ -179,17 +1474,21 @@ def _knn_edges(nodes, epi, k_val, p_rewire, rnd):
179
1474
  choices = list(node_set - {u, v})
180
1475
  if choices:
181
1476
  v = rnd.choice(choices)
182
- new_edges.add(tuple(sorted((u, v))))
1477
+ new_edges.add(_ordered_edge(u, v))
183
1478
  return new_edges
184
1479
 
185
1480
 
186
- def _community_graph(comms, epi, nx):
1481
+ def _community_graph(
1482
+ comms: Iterable[Iterable[Hashable]],
1483
+ epi: Mapping[Hashable, float],
1484
+ nx: NetworkxModule,
1485
+ ) -> CommunityGraph:
187
1486
  """Return community graph ``C`` with mean EPI per community."""
188
1487
  C = nx.Graph()
189
1488
  for idx, comm in enumerate(comms):
190
1489
  members = list(comm)
191
1490
  try:
192
- epi_mean = fmean(epi[n] for n in members)
1491
+ epi_mean = fmean(_as_float(epi.get(n)) for n in members)
193
1492
  except StatisticsError:
194
1493
  epi_mean = 0.0
195
1494
  C.add_node(idx)
@@ -197,16 +1496,21 @@ def _community_graph(comms, epi, nx):
197
1496
  C.nodes[idx]["members"] = members
198
1497
  for i, j in combinations(C.nodes(), 2):
199
1498
  w = abs(
200
- get_attr(C.nodes[i], ALIAS_EPI, 0.0)
201
- - get_attr(C.nodes[j], ALIAS_EPI, 0.0)
1499
+ _as_float(get_attr(C.nodes[i], ALIAS_EPI, 0.0))
1500
+ - _as_float(get_attr(C.nodes[j], ALIAS_EPI, 0.0))
202
1501
  )
203
1502
  C.add_edge(i, j, weight=w)
204
- return C
1503
+ return cast(CommunityGraph, C)
205
1504
 
206
1505
 
207
- def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
1506
+ def _community_k_neighbor_edges(
1507
+ C: CommunityGraph,
1508
+ k_val: int,
1509
+ p_rewire: float,
1510
+ rnd: random.Random,
1511
+ ) -> tuple[set[RemeshEdge], dict[int, int], list[tuple[int, int, int]]]:
208
1512
  """Edges linking each community to its ``k`` nearest neighbours."""
209
- epi_vals = {n: get_attr(C.nodes[n], ALIAS_EPI, 0.0) for n in C.nodes()}
1513
+ epi_vals = {n: _as_float(get_attr(C.nodes[n], ALIAS_EPI, 0.0)) for n in C.nodes()}
210
1514
  ordered = sorted(C.nodes(), key=lambda v: epi_vals[v])
211
1515
  new_edges = set()
212
1516
  attempts = {n: 0 for n in C.nodes()}
@@ -240,7 +1544,7 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
240
1544
  if choices:
241
1545
  v = rnd.choice(choices)
242
1546
  rewired_now = True
243
- new_edges.add(tuple(sorted((u, v))))
1547
+ new_edges.add(_ordered_edge(u, v))
244
1548
  attempts[u] += 1
245
1549
  if rewired_now:
246
1550
  rewired.append((u, original_v, v))
@@ -249,17 +1553,19 @@ def _community_k_neighbor_edges(C, k_val, p_rewire, rnd):
249
1553
 
250
1554
 
251
1555
  def _community_remesh(
252
- G,
253
- epi,
254
- k_val,
255
- p_rewire,
256
- rnd,
257
- nx,
258
- nx_comm,
259
- mst_edges,
260
- n_before,
261
- ):
1556
+ G: CommunityGraph,
1557
+ epi: Mapping[Hashable, float],
1558
+ k_val: int,
1559
+ p_rewire: float,
1560
+ rnd: random.Random,
1561
+ nx: NetworkxModule,
1562
+ nx_comm: CommunityModule,
1563
+ mst_edges: Iterable[RemeshEdge],
1564
+ n_before: int,
1565
+ ) -> None:
262
1566
  """Remesh ``G`` replacing nodes by modular communities."""
1567
+ from ..glyph_history import append_metric
1568
+
263
1569
  comms = list(nx_comm.greedy_modularity_communities(G))
264
1570
  if len(comms) <= 1:
265
1571
  with edge_version_update(G):
@@ -268,7 +1574,7 @@ def _community_remesh(
268
1574
  return
269
1575
  C = _community_graph(comms, epi, nx)
270
1576
  mst_c = nx.minimum_spanning_tree(C, weight="weight")
271
- new_edges = set(mst_c.edges())
1577
+ new_edges: set[RemeshEdge] = {_ordered_edge(u, v) for u, v in mst_c.edges()}
272
1578
  extra_edges, attempts, rewired_edges = _community_k_neighbor_edges(
273
1579
  C, k_val, p_rewire, rnd
274
1580
  )
@@ -312,7 +1618,7 @@ def _community_remesh(
312
1618
 
313
1619
 
314
1620
  def apply_topological_remesh(
315
- G,
1621
+ G: CommunityGraph,
316
1622
  mode: str | None = None,
317
1623
  *,
318
1624
  k: int | None = None,
@@ -336,18 +1642,14 @@ def apply_topological_remesh(
336
1642
 
337
1643
  if mode is None:
338
1644
  mode = str(
339
- G.graph.get(
340
- "REMESH_MODE", REMESH_DEFAULTS.get("REMESH_MODE", "knn")
341
- )
1645
+ G.graph.get("REMESH_MODE", REMESH_DEFAULTS.get("REMESH_MODE", "knn"))
342
1646
  )
343
1647
  mode = str(mode)
344
1648
  nx, nx_comm = _get_networkx_modules()
345
- epi = {n: get_attr(G.nodes[n], ALIAS_EPI, 0.0) for n in nodes}
1649
+ epi = {n: _as_float(get_attr(G.nodes[n], ALIAS_EPI, 0.0)) for n in nodes}
346
1650
  mst_edges = _mst_edges_from_epi(nx, nodes, epi)
347
1651
  default_k = int(
348
- G.graph.get(
349
- "REMESH_COMMUNITY_K", REMESH_DEFAULTS.get("REMESH_COMMUNITY_K", 2)
350
- )
1652
+ G.graph.get("REMESH_COMMUNITY_K", REMESH_DEFAULTS.get("REMESH_COMMUNITY_K", 2))
351
1653
  )
352
1654
  k_val = max(1, int(k) if k is not None else default_k)
353
1655
 
@@ -374,7 +1676,11 @@ def apply_topological_remesh(
374
1676
  G.add_edges_from(new_edges)
375
1677
 
376
1678
 
377
- def _extra_gating_ok(hist, cfg, w_estab):
1679
+ def _extra_gating_ok(
1680
+ hist: MutableMapping[str, Sequence[float]],
1681
+ cfg: Mapping[str, RemeshConfigValue],
1682
+ w_estab: int,
1683
+ ) -> bool:
378
1684
  """Check additional stability gating conditions."""
379
1685
  checks = [
380
1686
  ("phase_sync", "REMESH_MIN_PHASE_SYNC", ge),
@@ -388,14 +1694,28 @@ def _extra_gating_ok(hist, cfg, w_estab):
388
1694
  if series is not None and len(series) >= w_estab:
389
1695
  win = series[-w_estab:]
390
1696
  avg = sum(win) / len(win)
391
- if not op(avg, cfg[cfg_key]):
1697
+ threshold = _as_float(cfg[cfg_key])
1698
+ if not op(avg, threshold):
392
1699
  return False
393
1700
  return True
394
1701
 
395
1702
 
396
1703
  def apply_remesh_if_globally_stable(
397
- G, pasos_estables_consecutivos: int | None = None
1704
+ G: CommunityGraph,
1705
+ stable_step_window: int | None = None,
1706
+ **kwargs: Any,
398
1707
  ) -> None:
1708
+ """Trigger remeshing when global stability indicators satisfy thresholds."""
1709
+
1710
+ from ..glyph_history import ensure_history
1711
+
1712
+ if kwargs:
1713
+ unexpected = ", ".join(sorted(kwargs))
1714
+ raise TypeError(
1715
+ "apply_remesh_if_globally_stable() got unexpected keyword argument(s): "
1716
+ f"{unexpected}"
1717
+ )
1718
+
399
1719
  params = [
400
1720
  (
401
1721
  "REMESH_STABILITY_WINDOW",
@@ -432,20 +1752,16 @@ def apply_remesh_if_globally_stable(
432
1752
  float,
433
1753
  REMESH_DEFAULTS["REMESH_MIN_SI_HI_FRAC"],
434
1754
  ),
435
- (
436
- "REMESH_COOLDOWN_VENTANA",
437
- int,
438
- REMESH_DEFAULTS["REMESH_COOLDOWN_VENTANA"],
439
- ),
1755
+ (COOLDOWN_KEY, int, REMESH_DEFAULTS[COOLDOWN_KEY]),
440
1756
  ("REMESH_COOLDOWN_TS", float, REMESH_DEFAULTS["REMESH_COOLDOWN_TS"]),
441
1757
  ]
442
1758
  cfg = {}
443
1759
  for key, conv, _default in params:
444
1760
  cfg[key] = conv(get_param(G, key))
445
- frac_req = float(get_param(G, "FRACTION_STABLE_REMESH"))
1761
+ frac_req = _as_float(get_param(G, "FRACTION_STABLE_REMESH"))
446
1762
  w_estab = (
447
- pasos_estables_consecutivos
448
- if pasos_estables_consecutivos is not None
1763
+ stable_step_window
1764
+ if stable_step_window is not None
449
1765
  else cfg["REMESH_STABILITY_WINDOW"]
450
1766
  )
451
1767
 
@@ -456,21 +1772,16 @@ def apply_remesh_if_globally_stable(
456
1772
  win_sf = sf[-w_estab:]
457
1773
  if not all(v >= frac_req for v in win_sf):
458
1774
  return
459
- if cfg["REMESH_REQUIRE_STABILITY"] and not _extra_gating_ok(
460
- hist, cfg, w_estab
461
- ):
1775
+ if cfg["REMESH_REQUIRE_STABILITY"] and not _extra_gating_ok(hist, cfg, w_estab):
462
1776
  return
463
1777
 
464
1778
  last = G.graph.get("_last_remesh_step", -(10**9))
465
1779
  step_idx = len(sf)
466
- if step_idx - last < cfg["REMESH_COOLDOWN_VENTANA"]:
1780
+ if step_idx - last < cfg[COOLDOWN_KEY]:
467
1781
  return
468
- t_now = float(G.graph.get("_t", 0.0))
469
- last_ts = float(G.graph.get("_last_remesh_ts", -1e12))
470
- if (
471
- cfg["REMESH_COOLDOWN_TS"] > 0
472
- and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]
473
- ):
1782
+ t_now = _as_float(G.graph.get("_t", 0.0))
1783
+ last_ts = _as_float(G.graph.get("_last_remesh_ts", -1e12))
1784
+ if cfg["REMESH_COOLDOWN_TS"] > 0 and (t_now - last_ts) < cfg["REMESH_COOLDOWN_TS"]:
474
1785
  return
475
1786
 
476
1787
  apply_network_remesh(G)
@@ -479,7 +1790,20 @@ def apply_remesh_if_globally_stable(
479
1790
 
480
1791
 
481
1792
  __all__ = [
1793
+ # Core remesh functions (existing API)
482
1794
  "apply_network_remesh",
483
1795
  "apply_topological_remesh",
484
1796
  "apply_remesh_if_globally_stable",
1797
+ # Phase 1: Structural memory & pattern recognition
1798
+ "StructuralIdentity",
1799
+ "structural_similarity",
1800
+ "structural_memory_match",
1801
+ "compute_structural_signature",
1802
+ "detect_recursive_patterns",
1803
+ "identify_pattern_origin",
1804
+ "propagate_structural_identity",
1805
+ "apply_network_remesh_with_memory",
1806
+ # Phase 2: Coherence preservation & validation
1807
+ "RemeshCoherenceLossError",
1808
+ "validate_coherence_preservation",
485
1809
  ]