TNFR Logo
TheoryLearnSoftwareResearch

On this page

TNFR

Resonant Fractal Nature Theory — a mathematical framework for coherent patterns on graph-coupled networks.

About
  • Project history
  • Editorial policy
  • Contact
Resources
  • GitHub
  • PyPI
  • DOI · Zenodo
Legal
  • MIT License
  • Citation
© 2026 TNFR project — MIT licensed.DOI 10.5281/zenodo.17602860
docs
grammar
PHYSICS_VERIFICATION.md
API_CONTRACTS.mdCANONICAL_OZ_SEQUENCES.mdEMPIRICAL_CONFRONTATION_EEG.mdREADME.mdSTRUCTURAL_FIELDS_TETRAD.mdSTRUCTURAL_INTERFACE_THEORY.md
theory
APPLIED_STRUCTURAL_ANALYSIS.mdCATALOG_TYPE_HYGIENE_PROGRAMME.mdDISSIPATIVE_AND_OPEN_SYSTEMS.mdEMERGENT_ONTOLOGY.mdEXTENDED_FIELDS_AND_DERIVED_QUANTITIES.mdFUNDAMENTAL_THEORY.mdGAUGE_SYMMETRY_AND_UNIFICATION.mdGLOSSARY.mdMATHEMATICAL_DYNAMICS_BASIS.mdMINIMAL_STRUCTURAL_DEGREES.mdNUCLEUS_A_PRIME_LADDER_ATLAS.mdNUCLEUS_B_EQUIVARIANCE_OBSTRUCTIONS.mdPHYSICAL_REGIME_CORRESPONDENCES.mdREADME.mdREMESH_INFINITY_DERIVATION.mdSTRUCTURAL_CONSERVATION_THEOREM.mdSTRUCTURAL_OPERATORS.mdSTRUCTURAL_STABILITY_AND_DYNAMICS.mdTNFR_BSD_RESEARCH_NOTES.mdTNFR_HODGE_RESEARCH_NOTES.mdTNFR_NAVIER_STOKES_RESEARCH_NOTES.mdTNFR_NUMBER_THEORY.mdTNFR_P_VS_NP_RESEARCH_NOTES.mdTNFR_RIEMANN_RESEARCH_NOTES.mdTNFR_VARIATIONAL_PRINCIPLE.mdTNFR_YANG_MILLS_RESEARCH_NOTES.mdTNFR.pdfUNIFIED_GRAMMAR_RULES.md
factorization-lab
analysis
analyze_patterns.pycertificate_manifest.py
benchmarks
benchmark_analysis.pybenchmark_expansion_suite.pyfull_spectrum_factorization.pypaley_gap_extended.pypaley_gap_smoke.pytest_benchmark_suite.py
demos
experiment_contexts
exp_0b1663cd19b7.jsonexp_0bf0054b7474.jsonexp_75a4c8ca616a.jsonexp_848ee0fd1857.jsonexp_f6fe00562193.jsonexp_fdf3da424e1e.json
failure_telemetry_batch.pyfeedback_integration_demo.pyintegration_demo_snapshots.dbseed_management_integration_demo.pysnapshot_integration_demo.pytrajectory_143.jsontrajectory_77.jsontrajectory_89.jsontrajectory_91.jsontrajectory_97.json
docs
FACTORING_PLAYBOOK.mdFALSE_POSITIVE_TEST_SUITE.mdOPERATOR_CERTIFICATES.mdROADMAP.mdSPECTRAL_ROUTE.md
experiment_contexts
exp_cebe1d9e7d8e.json
notebooks
spectral_history.ipynb
scripts
run_false_positive_tests.py
tests
run_false_positive_test_suite.pytest_cli.pytest_false_positive_methodology.pytest_false_positive_verifier.pytest_feedback_integration.pytest_partitioning.pytest_seed_management.pytest_self_opt_support.pytest_snapshot_system.pytest_spectral_paley.pytest_verification_robustness.py
tnfr_factorization
__init__.pyapi.pycli.pyfailure_telemetry.pyfeedback_adapter.pyfeedback_integration.pypartitioning.pyself_opt_support.pyspectral_paley.py
demo_snapshots.dbLICENSE_SNAPSHOT.mdPACKAGE_SUMMARY.mdREADME.mdseed_management.pysnapshot_system.pytest_certificate_hashing.pytest_installation.pyverification_trajectory_77.json
benchmarks
analyze_tetrad_universality.pyb0star_alpha_canonical_product_graphs.pybenchmark_optimization_tracks.pybenchmark_utils.pyboundary_vibration.pybridge_primes_riemann.pychiral_involution.pycli_utils.pycoherence_projector_sense_index.pycommutant_bridge.pycomposition_arithmetic.pyconfinement_zones_test.pyconservation_law_validation.pydirected_paley_bridge.pyemergent_arithmetic_pulse.pyemergent_atom_dynamics.pyemergent_atomic_shells.pyemergent_base_dimension.pyemergent_dimension_dynamics.pyemergent_fractal_pulse.pyemergent_fractal_simplex_dimension.pyemergent_integers_symmetry.pyemergent_musical_nfr.pyemergent_nfr_geometry.pyemergent_nfr_where.pyemergent_rationals.pyemergent_rhythm.pyemergent_screening.pyemergent_shell_cardinals.pyemergent_shell_ordering.pyemergent_simplex_dimension.pyemergent_substrate_symmetry.pyequivariance_wall.pyexternal_phase_gate_validation.pyfield_methods_battery.pygolden_residue_remesh_bridge.pyintegrated_force_regime_study.pyinverse_spectrum_to_symmetry.pyk_phi_safety_demo.pykuramoto_farey_bridge.pymissing_piece_bridge.pymultichannel_interface_benchmark.pynavier_stokes_recipe_bridge.pynodal_propagator_residue_bridge.pyns_moment_hierarchy_cascade.pyoperational_irreducibility.pypaley_bridge.pyphase_curvature_investigation.pyphase_wall.pyphi_s_confinement_investigation.pyprimes_as_consequence.pypulse_phase_coherence_budget.pyREADME.mdremesh_infinity_riemann_baseline.pyremesh_infinity_riemann_composed.pyremesh_infinity_riemann_modified_graph.pyremesh_infinity_riemann_operator.pyremesh_infinity_riemann_spectral_basis.pyremesh_infinity_riemann_spectral_robustness.pyremesh_infinity_riemann_spectral.pyresidue_phase_vs_riemann.pystructural_interface_benchmark.pytemporal_interface_benchmark.pytetrad_results_aggregate.pyu2_destabilization_irreversibility.pyuniversality_clusters.pyxi_c_fast_experiment.py
primality-test
benchmarks
comprehensive_benchmark.py
docs
ADVANCED_INTEGRATION.mdmathematical_foundation.mdperformance_analysis.md
examples
advanced_examples.pybasic_usage.py
tnfr_primality
__init__.py__main__.pyadvanced_cli.pyadvanced_core.pycli.pyconstants.pycore.pyoptimized.py
MANIFEST.inPACKAGE_SUMMARY.mdREADME.mdRELEASE_NOTES_v1.0.mdsetup.pytest_installation.py
tests
core_physics
__init__.pytest_conservation_laws.pytest_delta_nfr_computation_paths.pytest_delta_nfr.pytest_dispersion_coherence_sign_invariance.pytest_emergent_constants_guard.pytest_lyapunov_operators.pytest_nodal_equation.pytest_structural_triad.py
data
replay_manifests
sample_run
_manifest_summary.json_manifest.json_partition_files.txt.gz
self_opt_validation
seed_alpha
paley.json
seed_beta
integration.json
seed_gamma
unknown.json
self_optimization
test_run
partitioned
test_run
test_run_p0.jsontest_run_p1.json
_manifest_summary.json_manifest.json
engines
test_pattern_discovery_manifest.pytest_self_optimization_engine.py
mathematics
__init__.pytest_autodiff.pytest_backends.pytest_dissipative_dynamics.pytest_epi.pytest_factory_patterns.pytest_metrics.pytest_navier_stokes_refounded.pytest_number_theory_canonical.pytest_operators.pytest_residue_networks.pytest_riemann_nodal_pulse.pytest_riemann_pulse_coherence.pytest_spaces.pytest_transforms.pytest_validator.py
operators
test_canonical_operators_modern.pytest_grammar_canon.pytest_grammar_canonical_consistency.pytest_grammar_dynamics.pytest_operator_contracts.pytest_operator_strategies.py
parallel
test_fractal_partition_manifest.py
physics
test_conservation_gauge_unification.pytest_dissipative_conservation.pytest_emergent_chemistry.pytest_field_cache_invalidation.pytest_gauge.pytest_phase_transition.pytest_signatures.pytest_spectral_conservation.pytest_structural_diffusion.pytest_structural_integrity.pytest_symplectic_substrate.pytest_tetrad_bounds.pytest_variational.pytest_yang_mills_closure.pytest_yang_mills_derivability.pytest_yang_mills_scaling.pytest_yang_mills_structural_gap.pytest_yang_mills_u6_sweep.py
scripts
test_run_self_opt_validation.pytest_run_self_optimization.py
sdk
__init__.pytest_simple_advanced.py
__init__.pyconftest.pyREADME.mdtest_breast_cancer_phase_gate_demo.pytest_classical_mechanics.pytest_distributed_fft.pytest_external_phase_gate_validation.pytest_factorization_entrypoint.pytest_multichannel_interface.pytest_nodal_optimizer.pytest_phase_gate_api.pytest_replay_register_manifest.pytest_signal_confrontation.pytest_structural_interface_api.pytest_structural_interface_baselines.pytest_structural_interface_benchmark.pytest_temporal_interface.pytest_vectorized_coherence_length_regression.pytest_wine_quality_phase_gate_demo.pyutils.py
examples
01_foundations
01_hello_world.py02_musical_resonance.py03_network_formation.py04_operator_sequences.py05_coherence_evolution.py06_network_topologies.py07_phase_transitions.py08_emergent_phenomena.py09_visualization_suite.py10_simplified_sdk_showcase.py
02_physics_regimes
11_classical_limit_comparison.py115_operator_contract_audit.py12_classical_mechanics_demo.py13_quantum_mechanics_demo.py14_uncertainty_and_interference.py15_train_crossing_demo.py17_conservation_law_demo.py26_gauge_structure_demo.py27_variational_principle_demo.py28_dissipative_systems_demo.py29_lyapunov_stability_demo.py30_self_optimization_demo.py31_mathematical_constants_basis.py33_complex_field_unification.py34_conservation_protocol_suite.py35_tetrad_irreducibility.py36_grammar_violation_detector.py37_operator_tetrad_synergy.py38_grammar_energy_landscape.py39_nodal_equation_decomposition.py
03_riemann_zeta
157_nodal_pulse_phase_attack.py41_von_mangoldt_zeta_demo.py42_riemann_zeros_as_resonances.py43_prime_ladder_hamiltonian_demo.py44_weil_explicit_formula_demo.py45_li_keiper_demo.py46_weil_tnfr_positivity_demo.py47_alpha_sweep_demo.py48_admissible_family_sweep_demo.py49_nodeaware_gauge_sweep_demo.py50_uniform_coercivity_demo.py51_adaptive_coercivity_demo.py52_paley_gap_coercivity_demo.py53_lyapunov_spectral_positivity_demo.py54_hilbert_polya_demo.py55_structural_zero_density_demo.py56_spectral_emergence_demo.py57_admissible_rescaling_demo.py58_oscillatory_correction_demo.py
04_riemann_L_twisted
59_dirichlet_l_function_demo.py60_dirichlet_l_continuation_demo.py61_dirichlet_l_hamiltonian_demo.py62_dirichlet_weil_explicit_formula_demo.py63_dirichlet_li_keiper_demo.py64_twisted_weil_positivity_demo.py65_twisted_alpha_sweep_demo.py66_twisted_admissible_family_sweep_demo.py67_twisted_nodeaware_gauge_sweep_demo.py68_twisted_hermite_family_demo.py69_twisted_coercivity_uniform_demo.py70_twisted_paley_gap_coercivity_demo.py71_twisted_lyapunov_spectral_demo.py72_twisted_hilbert_polya_demo.py73_twisted_structural_zero_density_demo.py74_twisted_spectral_emergence_demo.py75_twisted_admissible_rescaling_demo.py76_twisted_oscillatory_correction_demo.py
05_type_hygiene
77_remesh_infinity_residue_split_demo.py78_nuf_type_signature_demo.py79_epi_type_signature_demo.py80_phi_type_signature_demo.py81_dnfr_type_signature_demo.py82_remesh_window_type_signature_demo.py83_delta_phi_max_type_signature_demo.py84_coupling_weights_type_signature_demo.py85_tetrad_closure_signature_demo.py86_currents_closure_signature_demo.py87_aggregates_closure_signature_demo.py88_urules_consistency_signature_demo.py89_operator_catalog_discipline_signature_demo.py
06_navier_stokes
158_navier_stokes_two_face_refounded.py
07_number_theory
100_prime_families_orbits.py101_numbers_as_coupled_network.py102_nodal_flow_primes_equilibria.py116_nuf_emergent_prime_visibility.py146_primality_grammatical_inertness.py147_numbers_as_free_monoid_words.py148_capacity_arm_carries_von_mangoldt.py149_p14_is_the_capacity_arm_operator.py153_structural_frequency_rank_cyclotomy.py40_arithmetic_number_theory.py94_generative_number_construction.py95_primes_from_spectral_waves.py96_spectral_vibration_of_coherence.py97_goldbach_additive_multiplicative.pyemergent_chemistry_particles_demo.py
08_emergent_geometry
103_emergent_substrate_meets_riemann.py106_per_node_polarization_geometry.py107_orthogonal_structure_emergent_geometry.py108_emergent_field_generating_structure.py112_structure_predicts_coherence_flow.py113_overdamped_projection_bridge.py114_substrate_conserved_quantities.py117_emergent_geometry_residue_graph.py118_emergent_vs_classical_operator.py119_phase_sector_directed_residue.py120_symmetry_wall_substrate_vs_spectrum.py121_canonical_symmetry_break_negative.py122_factorization_phase_sector.py123_symmetry_sector_decomposition.py124_emergent_metric_fractal_consistency.py125_node_is_the_emergent_substrate.py126_two_layers_base_fiber.py127_base_is_emergent_not_imposed.py128_base_substrate_coemergence.py129_spectral_gap_base_fiber_clock.py130_operators_break_substrate_charges.py131_coemergent_loop_convergence.py132_geometric_phase_holonomy.py133_psi_topological_defects.py134_spectral_dimension_heat_kernel.py135_arrow_of_time_h_theorem.py136_heat_kernel_coefficients.py137_synchronization_transition.py138_structure_frequency_synchronization.py139_grammar_formal_language.py140_grammar_automaton.py141_grammar_rule_decomposition.py142_grammar_operator_quotient.py143_glyphic_function_sublanguage.py144_branching_combinator.py145_syntactic_monoid_starfree.py150_emergent_grammatical_pattern_parry.py151_grammar_in_emergent_geometry.py152_operator_contract_tetrahedron.py154_conductor_annotated_qr_spectrum.py155_ontological_position_of_numbers.py156_emergence_directness_law.py98_emergent_symplectic_substrate.py99_structural_diffusion.pyunified_fields_showcase.py
09_millennium
109_p_vs_np_coherence_synthesis.py110_bsd_rank_structural_pressure.py111_hodge_discrete_and_honest_gap.py
10_applications
159_empirical_confrontation_pipeline.py90_phase_gate_monitor_demo.py91_breast_cancer_phase_gate_demo.py92_wine_quality_phase_gate_demo.py93_structural_interface_demo.pypytorch_cuda_demo.py
README.md
scripts
replay
__init__.pyregister_manifest.py
__init__.pyREADME.mdrebuild_failure_manifest.pyrun_reproducible_benchmarks.pyrun_self_opt_validation.pyrun_self_optimization.pytnfr_is_prime.pyvalidate_conservation_law.pyverify_internal_references.py
src
core
__init__.pyevaluation.py
tnfr
backends
__init__.pyjax_backend.pynumpy_backend.pyoptimized_numpy.pyREADME.mdtorch_backend.py
cli
__init__.py__init__.pyiarguments.pyarguments.pyiexecution.pyexecution.pyiinteractive_validator.pyREADME.mdutils.pyutils.pyi
compat
__init__.pydataclass.pyjsonschema_stub.pymatplotlib_stub.pynumpy_stub.pyREADME.md
config
__init__.py__init__.pyiconstants.pyconstants.pyidefaults_core.pydefaults_init.pydefaults_metric.pydefaults.pyfeature_flags.pyfeature_flags.pyiglyph_constants.pyoperator_names.pyoperator_names.pyiphysics_derivation.pyprecision_modes.pypresets.pypresets.pyiREADME.mdsecurity.pythresholds.pytnfr_config.py
constants
__init__.py__init__.pyialiases.pyaliases.pyicanonical.pymetric.pymetric.pyioperational.py
core
__init__.pycontainer.pydefault_implementations.pyexceptions.pyinterfaces.pyREADME.md
dynamics
__init__.py__init__.pyiadaptation.pyadaptation.pyiadaptive_sequences.pyadaptive_sequences.pyiadelic.pyadvanced_cache_optimizer.pyadvanced_fft_arithmetic.pyaliases.pyaliases.pyibifurcation.pycache_aware_fft_engine.pycanonical.pycanonical.pyicomputational_hub.pycoordination.pycoordination.pyidistributed_fft.pydnfr.pydnfr.pyidynamic_limits.pyemergent_centralization.pyemergent_integration_engine.pyfeedback.pyfeedback.pyifft_backend.pyfft_cache_coordinator.pyfft_dispatchers.pyfft_engine.pyfft_workers.pyfused_dnfr.pyhomeostasis.pyhomeostasis.pyiintegrators.pyintegrators.pyilearning.pylearning.pyimetabolism.pymulti_modal_cache.pynbody_tnfr.pynbody.pynodal_optimizer.pyoptimization_orchestrator.pypropagation.pyREADME.mdruntime.pyruntime.pyisampling.pysampling.pyiselectors.pyselectors.pyiself_optimizing_engine.pyspectral_structural_fusion.pystructural_cache.pystructural_clip.pysymplectic.pyunified_backend.pyunified_mathematical_cache_orchestrator.py
engines
computation
__init__.pyfft_engine.pyunified_fft_engine.pyunified_gpu_system.py
constants
__init__.pycanonical.pyoperational.py
integration
__init__.pyemergent_integration.py
pattern_discovery
__init__.pymathematical_patterns.pymulti_modal_cache.py
self_optimization
__init__.pyengine.py
__init__.pyREADME.md
errors
__init__.pycontextual.py
factorization
__init__.py
flatten
README.md
gamma
README.md
glyph_history
README.md
glyph_runtime
README.md
immutable
README.md
initialization
README.md
io
README.md
math
__init__.pyfields_symbolic.pygrammar_validators.pyoptimizer.pyREADME.mdsymbolic.py
mathematics
__init__.pybackend.pybackend.pyidynamics.pydynamics.pyiepi.pyepi.pyigenerators.pygenerators.pyiliouville.pymetrics.pymetrics.pyinumber_theory.pyoperators_factory.pyoperators_factory.pyioperators.pyoperators.pyioptimized_primality.pyprojection.pyprojection.pyiREADME.mdruntime.pyruntime.pyispaces.pyspaces.pyispectral.pytransforms.pytransforms.pyiunified_cache.pyunified_numerical.pyzeta.py
metrics
__init__.py__init__.pyibuffer_cache.pybuffer_cache.pyicache_utils.pycoherence.pycoherence.pyicommon.pycommon.pyicore.pycore.pyidiagnosis.pydiagnosis.pyiemergence.pyexport.pyexport.pyiglyph_timing.pyglyph_timing.pyilearning_metrics.pylearning_metrics.pyilocal_coherence.pyphase_coherence.pyphase_compatibility.pyREADME.mdreporting.pyreporting.pyisense_index.pysense_index.pyitelemetry.pytetrad.pytrig_cache.pytrig_cache.pyitrig.pytrig.pyi
multiscale
__init__.pyhierarchical.pyREADME.md
navier_stokes
__init__.pyconservative_face.pyoperator.py
node
README.md
observers
README.md
operators
network_analysis
__init__.pysource_detection.py
postconditions
__init__.pymutation.py
preconditions
__init__.pycoherence.pydissonance.pyemission.pymutation.pyreception.pyresonance.py
strategies
__init__.pydefaults.pygpu_strategies.pystrategy.py
__init__.py__init__.pyialgebra.pycanonical_patterns.pycascade.pycoherence.pycontraction.pycoupling.pycycle_detection.pydefinitions_base.pydefinitions.pydefinitions.pyidissonance.pyemission.pyexpansion.pygrammar_application.pygrammar_canon.pygrammar_context.pygrammar_core.pygrammar_dynamics.pygrammar_error_factory.pygrammar_memoization.pygrammar_patterns.pygrammar_telemetry.pygrammar_types.pygrammar_u6.pygrammar_validate.pygrammar.pygrammar.pyihamiltonian.pyhealth_analyzer.pyintrospection.pyjitter.pyjitter.pyilifecycle.pymetabolism.pymetrics_basic.pymetrics_core.pymetrics_network.pymetrics_structural.pymetrics_u6.pymetrics.pymutation.pynodal_equation.pyoperator_contracts.pypattern_detection.pypatterns.pyREADME.mdreception.pyrecursivity.pyregistry.pyregistry.pyiremesh.pyremesh.pyiresonance.pyself_organization.pysilence.pystructural_units.pytransition.py
parallel
__init__.pyauto_scaler.pydistributed.pyengine.pymonitoring.pypartitioner.pyREADME.md
performance
guardrails.py
physics
__init__.py_helpers.pycalibration.pycanonical.pycell.pyclassical_mechanics.pyconservation_gauge_unification.pyconservation.pydissipative_conservation.pyemergent_chemistry.pyemergent_particles.pyextended.pyfields.pygauge.pyintegrity.pyinteractions.pylife.pylyapunov.pypatterns.pyphase_transition.pyquantum_mechanics.pyREADME.mdsignatures.pyspectral_conservation.pyspectral_metrics.pystructural_diffusion.pysymplectic_substrate.pytelemetry.pyunified.pyvariational.pyvectorized_ops.py
primality
__init__.py
recipes
__init__.pycookbook.pyREADME.md
riemann
__init__.pyadmissible_family_sweep.pyadmissible_rescaling.pyaggregates_closure_signature.pyalpha_sweep.pyanalytic_continuation_dirichlet.pyanalytic_continuation.pycoercivity_uniform.pycoupling_weights_type_signature.pycurrents_closure_signature.pydelta_phi_max_type_signature.pydirichlet_l.pydnfr_type_signature.pyepi_type_signature.pyhilbert_polya.pyli_keiper.pylyapunov_spectral_positivity.pynodal_pulse.pynodeaware_gauge_sweep.pynuf_type_signature.pyoperator_catalog_discipline_signature.pyoperator.pyoscillatory_correction.pypaley_gap_coercivity.pyphi_type_signature.pyprime_ladder_hamiltonian.pypulse_coherence.pyremesh_infinity_residue_split.pyremesh_window_type_signature.pyspectral_emergence.pystructural_zero_density.pytelemetry.pytetrad_closure_signature.pytwisted_admissible_family_sweep.pytwisted_admissible_rescaling.pytwisted_alpha_sweep.pytwisted_coercivity_uniform.pytwisted_hermite_family.pytwisted_hilbert_polya.pytwisted_li_keiper.pytwisted_lyapunov_spectral_positivity.pytwisted_nodeaware_gauge_sweep.pytwisted_oscillatory_correction.pytwisted_paley_gap_coercivity.pytwisted_prime_ladder_hamiltonian.pytwisted_spectral_emergence.pytwisted_structural_zero_density.pytwisted_weil_explicit_formula.pytwisted_weil_positivity.pyurules_consistency_signature.pyvon_mangoldt.pyweil_explicit_formula.pyweil_positivity.py
schemas
__init__.pygrammar.jsonREADME.md
sdk
__init__.py__init__.pyiadaptive_system.pyadaptive_system.pyibuilders.pybuilders.pyifluent.pyfluent.pyiREADME.mdself_opt.pysimple.pytemplates.pytemplates.pyiutils.py
security
__init__.pycrypto.pydatabase.pyREADME.mdsubprocess.pyvalidation.py
sequencing
__init__.pypatterns.pyREADME.md
services
__init__.pyorchestrator.pyREADME.md
sparse
__init__.pyREADME.mdrepresentations.py
structural
README.md
telemetry
__init__.pycache_metrics.pycache_metrics.pyiconstants.pynu_f.pynu_f.pyiREADME.mdunified_telemetry_system.pyverbosity.pyverbosity.pyi
tools
__init__.pydomain_templates.pyREADME.mdsequence_generator.pytnfr_is_prime_cli_optimized.pytnfr_is_prime_cli.py
topology
__init__.pyasymmetry.pyREADME.md
utils
cache_layers.pycache.pycache.pyicallbacks.pycallbacks.pyichunks.pychunks.pyidata.pydata.pyifast_diameter.pygraph.pygraph.pyiinit.pyinit.pyiio.pyio.pyinumeric.pynumeric.pyiREADME.mdtopology.pyunified_cache.py
validation
__init__.py__init__.pyiaggregator.pybase.pycompatibility.pycompatibility.pyiconfig.pygraph.pygraph.pyihealth.pyinput_validation.pyinterface_baselines.pyinvariants.pymultichannel_interface.pyphase_gate.pyREADME.mdrules.pyrules.pyiruntime.pyruntime.pyisequence_validator.pysignal_confrontation.pysoft_filters.pysoft_filters.pyispectral.pyspectral.pyistructural_interface.pytemporal_interface.pyunified_validation_system.pyvalidator.pywindow.pywindow.pyi
visualization
__init__.pycascade_viz.pyhierarchy.pyREADME.mdsequence_plotter.py
yang_mills
__init__.pyclosure.pyderivability.pyscaling.pystructural_gap.pyu6_sweep.py
__init__.py__init__.pyi_compat.py_version.py_version.pyialias.pyalias.pyibackend_config.pycache.pycache.pyiexecution.pyexecution.pyiflatten.pyflatten.pyigamma.pygamma.pyiglyph_history.pyglyph_history.pyiglyph_runtime.pyglyph_runtime.pyiimmutable.pyimmutable.pyiinitialization.pyinitialization.pyiio.pyio.pyilocking.pylocking.pyinode.pynode.pyiobservers.pyobservers.pyiontosim.pyontosim.pyipy.typedrng.pyrng.pyisecure_config.pyselector.pyselector.pyisense.pysense.pyistructural.pystructural.pyitokens.pytokens.pyitrace.pytrace.pyitypes.pytypes.pyiunits.pyunits.pyi
tetrad_evaluator.py
.pre-commit-config.yaml.semgrep.yaml.zenodo.jsonARCHITECTURE.mdbandit.yamlCHANGELOG.mdCITATION.cffCONTRIBUTING.mdEMERGENT_CANON_AUDIT.mdEMERGENT_DERIVATION_PLAN.mdLICENSE.mdMakefileMANIFEST.inpyproject.tomlpyrightconfig.jsonPYTORCH_CUDA_INTEGRATION.mdREADME.mdSECURITY.mdTESTING.mdTNFR_Website_Content_Brief.md
FILE: src/tnfr/operators/__init__.py

__init__.py

Network operators.

Operator helpers interact with TNFR graphs adhering to :class:tnfr.types.GraphLike, relying on nodes/neighbors views, number_of_nodes and the graph-level .graph metadata when applying structural transformations.

Source Code

python
"""Network operators.

Operator helpers interact with TNFR graphs adhering to
:class:`tnfr.types.GraphLike`, relying on ``nodes``/``neighbors`` views,
``number_of_nodes`` and the graph-level ``.graph`` metadata when applying
structural transformations.
"""

from __future__ import annotations

import heapq
import math
from collections.abc import Callable, Iterator
from itertools import islice
from statistics import StatisticsError, fmean
from typing import TYPE_CHECKING, Any

from tnfr import glyph_history

from ..alias import get_attr
from ..constants import DEFAULTS, get_param
from ..constants.aliases import ALIAS_EPI, ALIAS_VF
from ..constants.canonical import UM_COMPAT_THRESHOLD as _UM_COMPAT_CANONICAL
from ..constants.canonical import (
    COHERENCE_RETENTION,
    COUPLING_FINE,
    COUPLING_GENTLE,
    COUPLING_MODERATE,
    DISSONANCE_AMPLIFICATION,
    EN_MIX_FACTOR,
    INV_PI,
    NUL_DENSIFICATION_FACTOR,
    NUL_SCALE_FACTOR,
    SHA_VF_FACTOR,
    VAL_SCALE_FACTOR,
)
from ..errors import TNFRValueError
from ..metrics.trig import neighbor_phase_mean
from ..rng import make_rng
from ..types import EPIValue, Glyph, NodeId, TNFRGraph
from ..utils import angle_diff, get_nodenx
from . import definitions as _definitions
from .jitter import (
    JitterCache,
    JitterCacheManager,
    get_jitter_manager,
    random_jitter,
    reset_jitter_manager,
)
from .registry import OPERATORS, discover_operators, get_operator_class
from .remesh import (
    apply_network_remesh,
    apply_remesh_if_globally_stable,
    apply_topological_remesh,
)

_remesh_doc = (
    "Trigger a remesh once the stability window is satisfied.\n\n"
    "Parameters\n----------\n"
    "stable_step_window : int | None\n"
    "    Number of consecutive stable steps required before remeshing.\n"
    "    Only the English keyword 'stable_step_window' is supported."
)
if apply_remesh_if_globally_stable.__doc__:
    apply_remesh_if_globally_stable.__doc__ += "\n\n" + _remesh_doc
else:
    apply_remesh_if_globally_stable.__doc__ = _remesh_doc

discover_operators()

_DEFINITION_EXPORTS = {
    name: getattr(_definitions, name) for name in getattr(_definitions, "__all__", ())
}
globals().update(_DEFINITION_EXPORTS)

if TYPE_CHECKING:  # pragma: no cover - type checking only
    from ..node import NodeProtocol

GlyphFactors = dict[str, Any]
GlyphOperation = Callable[["NodeProtocol", GlyphFactors], None]

from .grammar import apply_glyph_with_grammar  # noqa: E402
from .hamiltonian import (  # noqa: E402
    InternalHamiltonian,
    build_H_coherence,
    build_H_coupling,
    build_H_frequency,
)
from .health_analyzer import SequenceHealthAnalyzer, SequenceHealthMetrics  # noqa: E402
from .pattern_detection import (  # noqa: E402
    PatternMatch,
    UnifiedPatternDetector,
    analyze_sequence,
    detect_pattern,
)

__all__ = [
    "JitterCache",
    "JitterCacheManager",
    "get_jitter_manager",
    "reset_jitter_manager",
    "random_jitter",
    "get_neighbor_epi",
    "get_glyph_factors",
    "GLYPH_OPERATIONS",
    "apply_glyph_obj",
    "apply_glyph",
    "apply_glyph_with_grammar",
    "apply_network_remesh",
    "apply_topological_remesh",
    "apply_remesh_if_globally_stable",
    "OPERATORS",
    "discover_operators",
    "get_operator_class",
    "SequenceHealthMetrics",
    "SequenceHealthAnalyzer",
    "InternalHamiltonian",
    "build_H_coherence",
    "build_H_frequency",
    "build_H_coupling",
    # Pattern detection (unified module)
    "PatternMatch",
    "UnifiedPatternDetector",
    "detect_pattern",
    "analyze_sequence",
]

__all__.extend(_DEFINITION_EXPORTS.keys())


def get_glyph_factors(node: NodeProtocol) -> GlyphFactors:
    """Fetch glyph tuning factors for a node.

    The glyph factors expose per-operator coefficients that modulate how an
    operator reorganizes a node's Primary Information Structure (EPI),
    structural frequency (νf), internal reorganization differential (ΔNFR), and
    phase. Missing factors fall back to the canonical defaults stored at the
    graph level.

    Parameters
    ----------
    node : NodeProtocol
        TNFR node providing a ``graph`` mapping where glyph factors may be
        cached under ``"GLYPH_FACTORS"``.

    Returns
    -------
    GlyphFactors
        Mapping with operator-specific coefficients merged with the canonical
        defaults. Mutating the returned mapping does not affect the graph.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self):
    ...         self.graph = {"GLYPH_FACTORS": {"AL_boost": 0.2}}
    >>> node = MockNode()
    >>> factors = get_glyph_factors(node)
    >>> factors["AL_boost"]
    0.2
    >>> factors["EN_mix"]  # Fallback to the default reception mix
    0.25
    """
    return node.graph.get("GLYPH_FACTORS", DEFAULTS["GLYPH_FACTORS"].copy())


def get_factor(gf: GlyphFactors, key: str, default: float) -> float:
    """Return a glyph factor as ``float`` with a default fallback.

    Parameters
    ----------
    gf : GlyphFactors
        Mapping of glyph names to numeric factors.
    key : str
        Factor identifier to look up.
    default : float
        Value used when ``key`` is absent. This typically corresponds to the
        canonical operator tuning and protects structural invariants.

    Returns
    -------
    float
        The resolved factor converted to ``float``.

    Notes
    -----
    This function performs defensive validation to ensure numeric safety.
    Invalid values (non-numeric, nan, inf) are silently replaced with the
    default to prevent operator failures. For strict validation, use
    ``validate_glyph_factors`` before passing factors to operators.

    Examples
    --------
    >>> get_factor({"AL_boost": 0.3}, "AL_boost", 0.05)
    0.3
    >>> get_factor({}, "IL_dnfr_factor", 0.7)
    0.7
    """
    value = gf.get(key, default)
    # Defensive validation: ensure the value is numeric and finite
    # Use default for invalid values to prevent operator failures
    if not isinstance(value, (int, float, str)):
        return default
    try:
        value = float(value)
    except (ValueError, TypeError):
        return default
    if not math.isfinite(value):
        return default
    return value


# -------------------------
# Glyphs (local operators)
# -------------------------


def get_neighbor_epi(node: NodeProtocol) -> tuple[list[NodeProtocol], EPIValue]:
    """Collect neighbour nodes and their mean EPI.

    The neighbour EPI is used by reception-like glyphs (e.g., EN, RA) to
    harmonise the node's EPI with the surrounding field without mutating νf,
    ΔNFR, or phase. When a neighbour lacks a direct ``EPI`` attribute the
    function resolves it from NetworkX metadata using known aliases.

    Parameters
    ----------
    node : NodeProtocol
        Node whose neighbours participate in the averaging.

    Returns
    -------
    list of NodeProtocol
        Concrete neighbour objects that expose TNFR attributes.
    EPIValue
        Arithmetic mean of the neighbouring EPIs. Equals the node EPI when no
        valid neighbours are found, allowing glyphs to preserve the node state.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi, neighbors):
    ...         self.EPI = epi
    ...         self._neighbors = neighbors
    ...         self.graph = {}
    ...     def neighbors(self):
    ...         return self._neighbors
    >>> neigh_a = MockNode(1.0, [])
    >>> neigh_b = MockNode(2.0, [])
    >>> node = MockNode(0.5, [neigh_a, neigh_b])
    >>> neighbors, epi_bar = get_neighbor_epi(node)
    >>> len(neighbors), round(epi_bar, 2)
    (2, 1.5)
    """

    epi = node.EPI
    neigh = list(node.neighbors())
    if not neigh:
        return [], epi

    if hasattr(node, "G"):
        G = node.G
        total = 0.0
        count = 0
        has_valid_neighbor = False
        needs_conversion = False
        for v in neigh:
            if hasattr(v, "EPI"):
                total += float(v.EPI)
                has_valid_neighbor = True
            else:
                attr = get_attr(G.nodes[v], ALIAS_EPI, None)
                if attr is not None:
                    total += float(attr)
                    has_valid_neighbor = True
                else:
                    total += float(epi)
                needs_conversion = True
            count += 1
        if not has_valid_neighbor:
            return [], epi
        epi_bar = total / count if count else float(epi)
        if needs_conversion:
            NodeNX = get_nodenx()
            if NodeNX is None:
                raise ImportError("NodeNX is unavailable")
            neigh = [
                v if hasattr(v, "EPI") else NodeNX.from_graph(node.G, v) for v in neigh
            ]
    else:
        try:
            epi_bar = fmean(v.EPI for v in neigh)
        except StatisticsError:
            epi_bar = epi

    return neigh, epi_bar


def _determine_dominant(
    neigh: list[NodeProtocol], default_kind: str
) -> tuple[str, float]:
    """Resolve the dominant ``epi_kind`` across neighbours.

    The dominant kind guides glyphs that synchronise EPI, ensuring that
    reshaping a node's EPI also maintains a coherent semantic label for the
    structural phase space.

    Parameters
    ----------
    neigh : list of NodeProtocol
        Neighbouring nodes providing EPI magnitude and semantic kind.
    default_kind : str
        Fallback label when no neighbour exposes an ``epi_kind``.

    Returns
    -------
    tuple of (str, float)
        The dominant ``epi_kind`` together with the maximum absolute EPI. The
        amplitude assists downstream logic when choosing between the node's own
        label and the neighbour-driven kind.

    Examples
    --------
    >>> class Mock:
    ...     def __init__(self, epi, kind):
    ...         self.EPI = epi
    ...         self.epi_kind = kind
    >>> _determine_dominant([Mock(0.2, "seed"), Mock(-1.0, "pulse")], "seed")
    ('pulse', 1.0)
    """
    best_kind: str | None = None
    best_abs = 0.0
    for v in neigh:
        abs_v = abs(v.EPI)
        if abs_v > best_abs:
            best_abs = abs_v
            best_kind = v.epi_kind
    if not best_kind:
        return default_kind, 0.0
    return best_kind, best_abs


def _mix_epi_with_neighbors(
    node: NodeProtocol, mix: float, default_glyph: Glyph | str
) -> tuple[float, str]:
    """Blend node EPI with the neighbour field and update its semantic label.

    The routine is shared by reception-like glyphs. It interpolates between the
    node EPI and the neighbour mean while selecting a dominant ``epi_kind``.
    ΔNFR, νf, and phase remain untouched; the function focuses on reconciling
    form.

    Parameters
    ----------
    node : NodeProtocol
        Node that exposes ``EPI`` and ``epi_kind`` attributes.
    mix : float
        Interpolation weight for the neighbour mean. ``mix = 0`` preserves the
        current EPI, while ``mix = 1`` adopts the average neighbour field.
    default_glyph : Glyph or str
        Glyph driving the mix. Its value informs the fallback ``epi_kind``.

    Returns
    -------
    tuple of (float, str)
        The neighbour mean EPI and the resolved ``epi_kind`` after mixing.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi, kind, neighbors):
    ...         self.EPI = epi
    ...         self.epi_kind = kind
    ...         self.graph = {}
    ...         self._neighbors = neighbors
    ...     def neighbors(self):
    ...         return self._neighbors
    >>> neigh = [MockNode(0.8, "wave", []), MockNode(1.2, "wave", [])]
    >>> node = MockNode(0.0, "seed", neigh)
    >>> _, kind = _mix_epi_with_neighbors(node, 0.5, Glyph.EN)
    >>> round(node.EPI, 2), kind
    (0.5, 'wave')
    """
    default_kind = (
        default_glyph.value if isinstance(default_glyph, Glyph) else str(default_glyph)
    )
    epi = node.EPI
    neigh, epi_bar = get_neighbor_epi(node)

    if not neigh:
        node.epi_kind = default_kind
        return epi, default_kind

    dominant, best_abs = _determine_dominant(neigh, default_kind)
    new_epi = (1 - mix) * epi + mix * epi_bar
    _set_epi_with_boundary_check(node, new_epi)
    final = dominant if best_abs > abs(new_epi) else node.epi_kind
    if not final:
        final = default_kind
    node.epi_kind = final
    return epi_bar, final


def _op_AL(node: NodeProtocol, gf: GlyphFactors) -> None:  # AL — Emission
    """Amplify the node EPI via the Emission glyph.

    Emission injects additional coherence into the node by boosting its EPI
    without touching νf, ΔNFR, or phase. The boost amplitude is controlled by
    ``AL_boost``.

    Parameters
    ----------
    node : NodeProtocol
        Node whose EPI is increased.
    gf : GlyphFactors
        Factor mapping used to resolve ``AL_boost``.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi):
    ...         self.EPI = epi
    ...         self.graph = {}
    >>> node = MockNode(0.8)
    >>> _op_AL(node, {"AL_boost": 0.2})
    >>> node.EPI <= 1.0  # Bounded by structural_clip
    True
    """
    f = get_factor(gf, "AL_boost", COUPLING_GENTLE)
    new_epi = node.EPI + f
    _set_epi_with_boundary_check(node, new_epi)


def _op_EN(node: NodeProtocol, gf: GlyphFactors) -> None:  # EN — Reception
    """Mix the node EPI with the neighbour field via Reception.

    Reception reorganizes the node's EPI towards the neighbourhood mean while
    choosing a coherent ``epi_kind``. νf, ΔNFR, and phase remain unchanged.

    Parameters
    ----------
    node : NodeProtocol
        Node whose EPI is being reconciled.
    gf : GlyphFactors
        Source of the ``EN_mix`` blending coefficient.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi, neighbors):
    ...         self.EPI = epi
    ...         self.epi_kind = "seed"
    ...         self.graph = {}
    ...         self._neighbors = neighbors
    ...     def neighbors(self):
    ...         return self._neighbors
    >>> neigh = [MockNode(1.0, []), MockNode(0.0, [])]
    >>> node = MockNode(0.4, neigh)
    >>> _op_EN(node, {"EN_mix": 0.5})
    >>> round(node.EPI, 2)
    0.7
    """
    mix = get_factor(gf, "EN_mix", 0.25)
    _mix_epi_with_neighbors(node, mix, Glyph.EN)


def _op_IL(node: NodeProtocol, gf: GlyphFactors) -> None:  # IL — Coherence
    """Dampen ΔNFR magnitudes through the Coherence glyph.

    Coherence contracts the internal reorganization differential (ΔNFR) while
    leaving EPI, νf, and phase untouched. The contraction preserves the sign of
    ΔNFR, increasing structural stability.

    Parameters
    ----------
    node : NodeProtocol
        Node whose ΔNFR is being scaled.
    gf : GlyphFactors
        Provides ``IL_dnfr_factor`` controlling the contraction strength.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, dnfr):
    ...         self.dnfr = dnfr
    >>> node = MockNode(0.5)
    >>> _op_IL(node, {"IL_dnfr_factor": 0.2})
    >>> node.dnfr
    0.1
    """
    factor = get_factor(gf, "IL_dnfr_factor", COHERENCE_RETENTION)
    node.dnfr = factor * getattr(node, "dnfr", 0.0)


def _op_OZ(node: NodeProtocol, gf: GlyphFactors) -> None:  # OZ — Dissonance
    """Excite ΔNFR through the Dissonance glyph.

    Dissonance amplifies ΔNFR or injects jitter, testing the node's stability.
    EPI, νf, and phase remain unaffected while ΔNFR grows to trigger potential
    bifurcations.

    Parameters
    ----------
    node : NodeProtocol
        Node whose ΔNFR is being stressed.
    gf : GlyphFactors
        Supplies ``OZ_dnfr_factor`` and optional noise parameters.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, dnfr):
    ...         self.dnfr = dnfr
    ...         self.graph = {}
    >>> node = MockNode(0.2)
    >>> _op_OZ(node, {"OZ_dnfr_factor": 2.0})
    >>> node.dnfr
    0.4
    """
    factor = get_factor(gf, "OZ_dnfr_factor", DISSONANCE_AMPLIFICATION)
    dnfr = getattr(node, "dnfr", 0.0)
    if bool(node.graph.get("OZ_NOISE_MODE", False)):
        sigma = float(node.graph.get("OZ_SIGMA", 0.1))
        if sigma <= 0:
            node.dnfr = dnfr
            return
        node.dnfr = dnfr + random_jitter(node, sigma)
    else:
        node.dnfr = factor * dnfr if abs(dnfr) > 1e-9 else 0.1


def _um_candidate_iter(node: NodeProtocol) -> Iterator[NodeProtocol]:
    sample_ids = node.graph.get("_node_sample")
    if sample_ids is not None and hasattr(node, "G"):
        NodeNX = get_nodenx()
        if NodeNX is None:
            raise ImportError("NodeNX is unavailable")
        base = (NodeNX.from_graph(node.G, j) for j in sample_ids)
    else:
        base = node.all_nodes()
    for j in base:
        same = (j is node) or (getattr(node, "n", None) == getattr(j, "n", None))
        if same or node.has_edge(j):
            continue
        yield j


def _um_select_candidates(
    node: NodeProtocol,
    candidates: Iterator[NodeProtocol],
    limit: int,
    mode: str,
    th: float,
) -> list[NodeProtocol]:
    """Select a subset of ``candidates`` for UM coupling."""
    rng = make_rng(int(node.graph.get("RANDOM_SEED", 0)), node.offset(), node.G)

    if limit <= 0:
        return list(candidates)

    if mode == "proximity":
        return heapq.nsmallest(
            limit, candidates, key=lambda j: abs(angle_diff(j.theta, th))
        )

    reservoir = list(islice(candidates, limit))
    for i, cand in enumerate(candidates, start=limit):
        j = rng.randint(0, i)
        if j < limit:
            reservoir[j] = cand

    if mode == "sample":
        rng.shuffle(reservoir)

    return reservoir


def compute_consensus_phase(phases: list[float]) -> float:
    """Compute circular mean (consensus phase) from a list of phase angles.

    This function calculates the consensus phase using the circular mean
    formula: arctan2(mean(sin), mean(cos)). This ensures proper handling
    of phase wrapping at ±π boundaries.

    Parameters
    ----------
    phases : list[float]
        list of phase angles in radians.

    Returns
    -------
    float
        Consensus phase angle in radians, in the range [-π, π).

    Notes
    -----
    The consensus phase represents the central tendency of a set of angular
    values, accounting for the circular nature of phase space. This is
    critical for bidirectional phase synchronization in the UM operator.

    Examples
    --------
    >>> import math
    >>> phases = [0.0, math.pi/2, math.pi]
    >>> result = compute_consensus_phase(phases)
    >>> -math.pi <= result < math.pi
    True
    """
    if not phases:
        return 0.0

    cos_sum = sum(math.cos(ph) for ph in phases)
    sin_sum = sum(math.sin(ph) for ph in phases)
    return math.atan2(sin_sum, cos_sum)


def _op_UM(node: NodeProtocol, gf: GlyphFactors) -> None:  # UM — Coupling
    """Align node phase and frequency with neighbours and optionally create links.

    Coupling shifts the node phase ``theta`` towards the neighbour mean while
    respecting νf and EPI. When bidirectional mode is enabled (default), both
    the node and its neighbors synchronize their phases mutually. Additionally,
    structural frequency (νf) synchronization causes coupled nodes to converge
    their reorganization rates. Coupling also reduces ΔNFR through mutual
    stabilization, decreasing reorganization pressure proportional to phase
    alignment strength. When functional links are enabled it may add edges
    based on combined phase, EPI, and sense-index similarity.

    Parameters
    ----------
    node : NodeProtocol
        Node whose phase and frequency are being synchronised.
    gf : GlyphFactors
        Provides ``UM_theta_push``, ``UM_vf_sync``, ``UM_dnfr_reduction`` and
        optional selection parameters.

    Notes
    -----
    Bidirectional synchronization (UM_BIDIRECTIONAL=True, default) implements
    the canonical TNFR requirement φᵢ(t) ≈ φⱼ(t) by mutually adjusting phases
    of both the node and its neighbors towards a consensus phase. This ensures
    true coupling as defined in the theory.

    Structural frequency synchronization (UM_SYNC_VF=True, default) implements
    the TNFR requirement that coupling synchronizes not only phases but also
    structural frequencies (νf). This enables coupled nodes to converge their
    reorganization rates, which is essential for sustained resonance and coherent
    network evolution as described by the nodal equation: ∂EPI/∂t = νf · ΔNFR(t).

    ΔNFR stabilization (UM_STABILIZE_DNFR=True, default) implements the canonical
    effect where coupling reduces reorganization pressure through mutual stabilization.
    The reduction is proportional to phase alignment: well-coupled nodes (high phase
    alignment) experience stronger ΔNFR reduction, promoting structural coherence.

    Legacy unidirectional mode (UM_BIDIRECTIONAL=False) only adjusts the node's
    phase towards its neighbors, preserving backward compatibility.

    Examples
    --------
    >>> import math
    >>> class MockNode:
    ...     def __init__(self, theta, neighbors):
    ...         self.theta = theta
    ...         self.EPI = 1.0
    ...         self.Si = 0.5
    ...         self.graph = {}
    ...         self._neighbors = neighbors
    ...     def neighbors(self):
    ...         return self._neighbors
    ...     def offset(self):
    ...         return 0
    ...     def all_nodes(self):
    ...         return []
    ...     def has_edge(self, _):
    ...         return False
    ...     def add_edge(self, *_):
    ...         raise AssertionError("not used in example")
    >>> neighbor = MockNode(math.pi / 2, [])
    >>> node = MockNode(0.0, [neighbor])
    >>> _op_UM(node, {"UM_theta_push": 0.5})
    >>> round(node.theta, 2)
    0.79
    """
    k = get_factor(gf, "UM_theta_push", EN_MIX_FACTOR)
    k_vf = get_factor(gf, "UM_vf_sync", COUPLING_GENTLE)
    th_i = node.theta

    # Check if bidirectional synchronization is enabled (default: True)
    bidirectional = bool(node.graph.get("UM_BIDIRECTIONAL", True))

    if bidirectional:
        # Bidirectional mode: mutually synchronize node and neighbors
        neighbor_ids = list(node.neighbors())
        if neighbor_ids:
            # Get NodeNX wrapper for accessing neighbor attributes
            NodeNX = get_nodenx()
            if NodeNX is None or not hasattr(node, "G"):
                # Fallback to unidirectional if NodeNX unavailable
                thL = neighbor_phase_mean(node)
                d = angle_diff(thL, th_i)
                node.theta = th_i + k * d
            else:
                # Wrap neighbor IDs to access theta attribute
                neighbors = [NodeNX.from_graph(node.G, nid) for nid in neighbor_ids]

                # Collect all phases (node + neighbors)
                phases = [th_i] + [n.theta for n in neighbors]
                target_phase = compute_consensus_phase(phases)

                # Adjust node phase towards consensus
                node.theta = th_i + k * angle_diff(target_phase, th_i)

                # Adjust neighbor phases towards consensus
                for neighbor in neighbors:
                    th_j = neighbor.theta
                    neighbor.theta = th_j + k * angle_diff(target_phase, th_j)
    else:
        # Legacy unidirectional mode: only adjust node towards neighbors
        thL = neighbor_phase_mean(node)
        d = angle_diff(thL, th_i)
        node.theta = th_i + k * d

    # Structural frequency (νf) synchronization
    # According to TNFR theory, coupling synchronizes both phase and frequency
    sync_vf = bool(node.graph.get("UM_SYNC_VF", True))
    if sync_vf:
        neighbor_ids = list(node.neighbors())
        if neighbor_ids and hasattr(node, "G"):
            # Canonical access to vf through alias system
            vf_i = node.vf
            vf_neighbors = [
                get_attr(node.G.nodes[nid], ALIAS_VF, 0.0) for nid in neighbor_ids
            ]

            if vf_neighbors:
                vf_mean = sum(vf_neighbors) / len(vf_neighbors)

                # Gradual convergence towards mean (similar to phase sync)
                node.vf = vf_i + k_vf * (vf_mean - vf_i)

    # ΔNFR reduction by mutual stabilization
    # Coupling produces a stabilizing effect that reduces reorganization pressure
    stabilize_dnfr = bool(node.graph.get("UM_STABILIZE_DNFR", True))

    if stabilize_dnfr:
        k_dnfr = get_factor(gf, "UM_dnfr_reduction", COUPLING_MODERATE)

        # Calculate compatibility with neighbors based on phase alignment
        neighbor_ids = list(node.neighbors())
        if neighbor_ids:
            # Get NodeNX wrapper for accessing neighbor attributes
            NodeNX = get_nodenx()
            if NodeNX is not None and hasattr(node, "G"):
                neighbors = [NodeNX.from_graph(node.G, nid) for nid in neighbor_ids]

                # Compute phase alignments with each neighbor
                phase_alignments = []
                # Compute phase alignment using canonical formula
                from ..metrics.phase_compatibility import (
                    compute_phase_coupling_strength,
                )

                for neighbor in neighbors:
                    alignment = compute_phase_coupling_strength(
                        node.theta, neighbor.theta
                    )
                    phase_alignments.append(alignment)

                # Mean alignment represents coupling strength
                mean_alignment = sum(phase_alignments) / len(phase_alignments)

                # Reduce ΔNFR proportionally to coupling strength
                # reduction_factor < 1.0 when well-coupled (high alignment)
                reduction_factor = 1.0 - (k_dnfr * mean_alignment)
                node.dnfr = node.dnfr * reduction_factor

    if bool(node.graph.get("UM_FUNCTIONAL_LINKS", True)):
        thr = float(
            node.graph.get(
                "UM_COMPAT_THRESHOLD",
                DEFAULTS.get("UM_COMPAT_THRESHOLD", _UM_COMPAT_CANONICAL),
            )
        )
        epi_i = node.EPI
        si_i = node.Si

        limit = int(node.graph.get("UM_CANDIDATE_COUNT", 0))
        mode = str(node.graph.get("UM_CANDIDATE_MODE", "sample")).lower()
        candidates = _um_select_candidates(
            node, _um_candidate_iter(node), limit, mode, th_i
        )

        # Use canonical phase coupling strength formula
        from ..metrics.phase_compatibility import compute_phase_coupling_strength

        for j in candidates:
            phase_coupling = compute_phase_coupling_strength(th_i, j.theta)

            epi_j = j.EPI
            si_j = j.Si
            epi_sim = 1.0 - abs(epi_i - epi_j) / (abs(epi_i) + abs(epi_j) + 1e-9)
            si_sim = 1.0 - abs(si_i - si_j)
            # Compatibility combines phase coupling (50%), EPI similarity (25%), Si similarity (25%)
            compat = phase_coupling * 0.5 + 0.25 * epi_sim + 0.25 * si_sim
            if compat >= thr:
                node.add_edge(j, compat)


def _op_RA(node: NodeProtocol, gf: GlyphFactors) -> None:  # RA — Resonance
    """Propagate coherence through resonance with νf amplification.

    Resonance (RA) propagates EPI along existing couplings while amplifying
    the structural frequency (νf) to reflect network coherence propagation.
    According to TNFR theory, RA creates "resonant cascades" where coherence
    amplifies across the network, increasing collective νf and global C(t).

    **Canonical Effects (always active):**

    - **EPI Propagation**: Diffuses EPI to neighbors (identity-preserving)
    - **νf Amplification**: Increases structural frequency when propagating coherence
    - **Phase Alignment**: Strengthens phase synchrony across propagation path
    - **Network C(t)**: Contributes to global coherence increase
    - **Identity Preservation**: Maintains structural identity during propagation

    Parameters
    ----------
    node : NodeProtocol
        Node harmonising with its neighbourhood.
    gf : GlyphFactors
        Provides ``RA_epi_diff`` (mixing coefficient, default 0.15),
        ``RA_vf_amplification`` (νf boost factor, default 0.05), and
        ``RA_phase_coupling`` (phase alignment factor, default 0.10).

    Notes
    -----
    **νf Amplification (Canonical)**: When neighbors have coherence (|epi_bar| > 1e-9),
    node.vf is multiplied by (1.0 + RA_vf_amplification). This reflects
    the canonical TNFR property that resonance amplifies collective νf.
    This is NOT optional - it is a fundamental property of resonance per TNFR theory.

    **Phase Alignment Strengthening (Canonical)**: RA strengthens phase alignment
    with neighbors by applying a small phase correction toward the network mean.
    This ensures that "Phase alignment: Strengthens across propagation path" as
    stated in the theoretical foundations. Uses existing phase utility functions
    to avoid code duplication.

    **Network Coherence Tracking (Optional)**: If ``TRACK_NETWORK_COHERENCE`` is enabled,
    global C(t) is measured before/after RA application to quantify network-level
    coherence increase.

    **Identity Preservation (Canonical)**: EPI structure (kind and sign) are preserved
    during propagation to ensure structural identity is maintained as required by theory.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi, neighbors):
    ...         self.EPI = epi
    ...         self.epi_kind = "seed"
    ...         self.vf = 1.0
    ...         self.theta = 0.0
    ...         self.graph = {}
    ...         self._neighbors = neighbors
    ...     def neighbors(self):
    ...         return self._neighbors
    >>> neighbor = MockNode(1.0, [])
    >>> neighbor.theta = 0.1
    >>> node = MockNode(0.2, [neighbor])
    >>> _op_RA(node, {"RA_epi_diff": 0.25, "RA_vf_amplification": 0.05})
    >>> round(node.EPI, 2)
    0.4
    >>> node.vf  # Amplified due to neighbor coherence (canonical effect)
    1.05
    """
    # Get configuration factors
    diff = get_factor(gf, "RA_epi_diff", COUPLING_MODERATE)
    vf_boost = get_factor(gf, "RA_vf_amplification", COUPLING_FINE)
    phase_coupling = get_factor(
        gf, "RA_phase_coupling", 0.10
    )  # Canonical phase strengthening

    # Track network C(t) before RA if enabled (optional telemetry)
    track_coherence = bool(node.graph.get("TRACK_NETWORK_COHERENCE", False))
    c_before = None
    if track_coherence and hasattr(node, "G"):
        try:
            from ..metrics.coherence import compute_network_coherence

            c_before = compute_network_coherence(node.G)
            if "_ra_c_tracking" not in node.graph:
                node.graph["_ra_c_tracking"] = []
        except ImportError:
            pass  # Metrics module not available

    # Capture state before for metrics
    vf_before = node.vf
    epi_before = node.EPI
    kind_before = node.epi_kind
    theta_before = node.theta if hasattr(node, "theta") else None

    # EPI diffusion (existing behavior)
    neigh, epi_bar = get_neighbor_epi(node)
    epi_bar_result, kind_result = _mix_epi_with_neighbors(node, diff, Glyph.RA)

    # CANONICAL EFFECT 1: νf amplification through resonance
    # This is always active - it's a fundamental property of resonance per TNFR theory
    # Only amplify if neighbors have coherence to propagate
    if abs(epi_bar_result) > 1e-9 and len(neigh) > 0:
        node.vf *= 1.0 + vf_boost

    # CANONICAL EFFECT 2: Phase alignment strengthening
    # Per theory: "Phase alignment: Strengthens across propagation path"
    # Uses existing phase locking logic from IL operator (avoid duplication)
    phase_strengthened = False
    if len(neigh) > 0 and hasattr(node, "theta") and hasattr(node, "G"):
        try:
            # Use existing phase locking utility from IL operator
            import cmath
            import math

            from ..alias import get_attr
            from ..constants.aliases import ALIAS_THETA

            # Get neighbor phases using existing utilities
            neighbor_phases = []
            for n in neigh:
                try:
                    theta_n = float(get_attr(n, ALIAS_THETA, 0.0))
                    neighbor_phases.append(theta_n)
                except (KeyError, ValueError, TypeError):
                    continue

            if neighbor_phases:
                # Circular mean using the same method as in phase_coherence.py
                complex_phases = [cmath.exp(1j * theta) for theta in neighbor_phases]
                mean_real = sum(z.real for z in complex_phases) / len(complex_phases)
                mean_imag = sum(z.imag for z in complex_phases) / len(complex_phases)
                mean_complex = complex(mean_real, mean_imag)
                mean_phase = cmath.phase(mean_complex)

                # Ensure positive phase [0, 2π]
                if mean_phase < 0:
                    mean_phase += 2 * math.pi

                # Calculate phase difference (shortest arc)
                delta_theta = mean_phase - node.theta
                if delta_theta > math.pi:
                    delta_theta -= 2 * math.pi
                elif delta_theta < -math.pi:
                    delta_theta += 2 * math.pi

                # Apply phase strengthening (move toward network mean)
                # Same approach as IL operator phase locking
                node.theta = node.theta + phase_coupling * delta_theta

                # Normalize to [0, 2π]
                node.theta = node.theta % (2 * math.pi)
                phase_strengthened = True
        except (AttributeError, ImportError):
            pass  # Phase alignment not possible in this context

    # Track identity preservation (canonical validation)
    identity_preserved = (
        kind_result == kind_before or kind_result == Glyph.RA.value
    ) and (
        float(epi_before) * float(node.EPI) >= 0
    )  # Sign preserved

    # Collect propagation metrics if enabled (optional telemetry)
    collect_metrics = bool(node.graph.get("COLLECT_RA_METRICS", False))
    if collect_metrics:
        metrics = {
            "operator": "RA",
            "epi_propagated": epi_bar_result,
            "vf_amplification": node.vf / vf_before if vf_before > 0 else 1.0,
            "neighbors_influenced": len(neigh),
            "identity_preserved": identity_preserved,
            "epi_before": epi_before,
            "epi_after": float(node.EPI),
            "vf_before": vf_before,
            "vf_after": node.vf,
            "phase_before": theta_before,
            "phase_after": node.theta if hasattr(node, "theta") else None,
            "phase_alignment_strengthened": phase_strengthened,
        }
        if "ra_metrics" not in node.graph:
            node.graph["ra_metrics"] = []
        node.graph["ra_metrics"].append(metrics)

    # Track network C(t) after RA if enabled (optional telemetry)
    if track_coherence and c_before is not None and hasattr(node, "G"):
        try:
            from ..metrics.coherence import compute_network_coherence

            c_after = compute_network_coherence(node.G)
            node.graph["_ra_c_tracking"].append(
                {
                    "node": getattr(node, "n", None),
                    "c_before": c_before,
                    "c_after": c_after,
                    "c_delta": c_after - c_before,
                }
            )
        except ImportError:
            pass


def _op_SHA(node: NodeProtocol, gf: GlyphFactors) -> None:  # SHA — Silence
    """Reduce νf while preserving EPI, ΔNFR, and phase.

    Silence decelerates a node by scaling νf (structural frequency) towards
    stillness. EPI, ΔNFR, and phase remain unchanged, signalling a temporary
    suspension of structural evolution.

    **TNFR Canonical Behavior:**

    According to the nodal equation ∂EPI/∂t = νf · ΔNFR(t), reducing νf → νf_min ≈ 0
    causes structural evolution to freeze (∂EPI/∂t → 0) regardless of ΔNFR magnitude.
    This implements **structural silence** - a state where the node's form (EPI) is
    preserved intact despite external pressures, enabling memory consolidation and
    protective latency.

    Parameters
    ----------
    node : NodeProtocol
        Node whose νf is being attenuated.
    gf : GlyphFactors
        Provides ``SHA_vf_factor`` to scale νf (default = SHA_VF_FACTOR, 0.9).

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, vf):
    ...         self.vf = vf
    >>> node = MockNode(1.0)
    >>> _op_SHA(node, {"SHA_vf_factor": 0.5})
    >>> node.vf
    0.5
    """
    factor = get_factor(gf, "SHA_vf_factor", SHA_VF_FACTOR)  # canonical ν_f↓ gain
    # Canonical SHA effect: reduce structural frequency toward zero
    # This implements: νf → νf_min ≈ 0 ⇒ ∂EPI/∂t → 0 (structural preservation)
    node.vf = factor * node.vf


factor_val = VAL_SCALE_FACTOR  # canonical Expansion ν_f↑ gain
factor_nul = NUL_SCALE_FACTOR  # canonical Contraction ν_f↓ gain
_SCALE_FACTORS = {Glyph.VAL: factor_val, Glyph.NUL: factor_nul}


def _set_epi_with_boundary_check(
    node: NodeProtocol, new_epi: float, *, apply_clip: bool = True
) -> None:
    """Canonical EPI assignment with structural boundary preservation.

    This is the unified function all operators should use when modifying EPI
    to ensure structural boundaries are respected. Provides single point of
    enforcement for TNFR canonical invariant: EPI ∈ [EPI_MIN, EPI_MAX].

    Parameters
    ----------
    node : NodeProtocol
        Node whose EPI is being updated
    new_epi : float
        New EPI value to assign
    apply_clip : bool, default True
        If True, applies structural_clip to enforce boundaries.
        If False, assigns value directly (use only when boundaries
        are known to be satisfied, e.g., from edge-aware pre-computation).

    Notes
    -----
    TNFR Principle: This function embodies the canonical invariant that EPI
    must remain within structural boundaries. All operator EPI modifications
    should flow through this function to maintain coherence.

    The function uses the graph-level configuration for EPI_MIN, EPI_MAX,
    and CLIP_MODE to ensure consistent boundary enforcement across all operators.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, epi):
    ...         self.EPI = epi
    ...         self.graph = {"EPI_MAX": 1.0, "EPI_MIN": -1.0}
    >>> node = MockNode(0.5)
    >>> _set_epi_with_boundary_check(node, 1.2)  # Will be clipped to 1.0
    >>> float(node.EPI)
    1.0
    """
    from ..dynamics.structural_clip import structural_clip

    if not apply_clip:
        node.EPI = new_epi
        return

    # Ensure new_epi is float (in case it's a BEPI or other structure)
    new_epi_float = float(new_epi)

    # Get boundary configuration from graph (with defensive fallback)
    graph_attrs = getattr(node, "graph", {})
    epi_min = float(graph_attrs.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
    epi_max = float(graph_attrs.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))
    clip_mode_str = str(graph_attrs.get("CLIP_MODE", "hard"))

    # Validate clip mode
    if clip_mode_str not in ("hard", "soft"):
        clip_mode_str = "hard"

    # Apply structural boundary preservation
    clipped_epi = structural_clip(
        new_epi_float,
        lo=epi_min,
        hi=epi_max,
        mode=clip_mode_str,  # type: ignore[arg-type]
        record_stats=False,
    )

    node.EPI = clipped_epi


def _compute_val_edge_aware_scale(
    epi_current: float, scale: float, epi_max: float, epsilon: float
) -> float:
    """Compute edge-aware scale factor for VAL (Expansion) operator.

    Adapts the expansion scale to prevent EPI overflow beyond EPI_MAX.
    When EPI is near the upper boundary, the effective scale is reduced
    to ensure EPI * scale_eff <= EPI_MAX.

    Parameters
    ----------
    epi_current : float
        Current EPI value
    scale : float
        Desired expansion scale factor (e.g., VAL_scale = 1.05)
    epi_max : float
        Upper EPI boundary (typically 1.0)
    epsilon : float
        Small value to prevent division by zero (e.g., 1e-12)

    Returns
    -------
    float
        Effective scale factor, adapted to respect EPI_MAX boundary

    Notes
    -----
    TNFR Principle: This implements "resonance to the edge" - expansion
    scales adaptively to explore volume while respecting structural envelope.
    The adaptation is a dynamic compatibility check, not a fixed constant.

    Examples
    --------
    >>> # Normal case: EPI far from boundary
    >>> _compute_val_edge_aware_scale(0.5, 1.05, 1.0, 1e-12)
    1.05

    >>> # Edge case: EPI near boundary, scale adapts
    >>> scale = _compute_val_edge_aware_scale(0.96, 1.05, 1.0, 1e-12)
    >>> abs(scale - 1.0417) < 0.001  # Roughly 1.0/0.96
    True
    """
    abs_epi = abs(epi_current)
    if abs_epi < epsilon:
        # EPI near zero, full scale can be applied safely
        return scale

    # Compute maximum safe scale that keeps EPI within bounds
    max_safe_scale = epi_max / abs_epi

    # Return the minimum of desired scale and safe scale
    return min(scale, max_safe_scale)


def _compute_nul_edge_aware_scale(
    epi_current: float, scale: float, epi_min: float, epsilon: float
) -> float:
    """Compute edge-aware scale factor for NUL (Contraction) operator.

    Adapts the contraction scale to prevent EPI underflow below EPI_MIN.

    Parameters
    ----------
    epi_current : float
        Current EPI value
    scale : float
        Desired contraction scale factor (e.g., NUL_scale = 0.9)
    epi_min : float
        Lower EPI boundary (typically -1.0)
    epsilon : float
        Small value to prevent division by zero (e.g., 1e-12)

    Returns
    -------
    float
        Effective scale factor, adapted to respect EPI_MIN boundary

    Notes
    -----
    TNFR Principle: Contraction concentrates structure toward core while
    maintaining coherence.

    For typical NUL_scale < 1.0, contraction naturally moves EPI toward zero
    (the center), which is always safe regardless of whether EPI is positive
    or negative. Edge-awareness is only needed if scale could somehow push
    EPI beyond boundaries.

    In practice, with NUL_scale = 0.9 < 1.0:
    - Positive EPI contracts toward zero: safe
    - Negative EPI contracts toward zero: safe

    Edge-awareness is provided for completeness and future extensibility.

    Examples
    --------
    >>> # Normal contraction (always safe with scale < 1.0)
    >>> _compute_nul_edge_aware_scale(0.5, 0.9, -1.0, 1e-12)
    0.9
    >>> _compute_nul_edge_aware_scale(-0.5, 0.9, -1.0, 1e-12)
    0.9
    """
    # With NUL_scale < 1.0, contraction moves toward zero (always safe)
    # No adaptation needed in typical case
    return scale


def _op_scale(node: NodeProtocol, factor: float) -> None:
    """Scale νf with the provided factor.

    Parameters
    ----------
    node : NodeProtocol
        Node whose νf is being updated.
    factor : float
        Multiplicative change applied to νf.
    """
    node.vf *= factor


def _make_scale_op(glyph: Glyph) -> GlyphOperation:
    def _op(node: NodeProtocol, gf: GlyphFactors) -> None:
        key = "VAL_scale" if glyph is Glyph.VAL else "NUL_scale"
        default = _SCALE_FACTORS[glyph]
        factor = get_factor(gf, key, default)

        # Always scale νf (existing behavior)
        _op_scale(node, factor)

        # NUL canonical ΔNFR densification (implements structural pressure concentration)
        if glyph is Glyph.NUL:
            # Volume reduction: V' = V · scale_factor (where scale_factor < 1.0)
            # Density increase: ρ_ΔNFR = ΔNFR / V' = ΔNFR / (V · scale_factor)
            # Result: ΔNFR' = ΔNFR · densification_factor
            #
            # Physics: when ν_f contracts by factor λ < 1, structural pressure
            # concentrates by 1/λ > 1 so the nodal-equation product νf·ΔNFR (the
            # EPI change rate) is conserved. For NUL_scale = 0.9, densification =
            # 1/0.9 ≈ 1.111. This is DERIVED from the contraction factor, not a
            # free magnitude (canonical NUL_DENSIFICATION_FACTOR).
            densification_key = "NUL_densification_factor"
            densification_default = NUL_DENSIFICATION_FACTOR  # = 1/λ (canonical)
            densification_factor = get_factor(
                gf, densification_key, densification_default
            )

            # Apply densification to ΔNFR (use lowercase dnfr for NodeProtocol)
            current_dnfr = node.dnfr
            node.dnfr = current_dnfr * densification_factor

            # Record densification telemetry for traceability
            telemetry = node.graph.setdefault("nul_densification_log", [])
            telemetry.append(
                {
                    "dnfr_before": current_dnfr,
                    "dnfr_after": float(node.dnfr),
                    "densification_factor": densification_factor,
                    "contraction_scale": factor,
                }
            )

        # Edge-aware EPI scaling (new behavior) if enabled
        edge_aware_enabled = bool(
            node.graph.get(
                "EDGE_AWARE_ENABLED", DEFAULTS.get("EDGE_AWARE_ENABLED", True)
            )
        )

        if edge_aware_enabled:
            epsilon = float(
                node.graph.get(
                    "EDGE_AWARE_EPSILON", DEFAULTS.get("EDGE_AWARE_EPSILON", 1e-12)
                )
            )
            epi_min = float(node.graph.get("EPI_MIN", DEFAULTS.get("EPI_MIN", -1.0)))
            epi_max = float(node.graph.get("EPI_MAX", DEFAULTS.get("EPI_MAX", 1.0)))

            epi_current = node.EPI

            # Compute edge-aware scale factor
            if glyph is Glyph.VAL:
                scale_eff = _compute_val_edge_aware_scale(
                    epi_current, factor, epi_max, epsilon
                )
            else:  # Glyph.NUL
                scale_eff = _compute_nul_edge_aware_scale(
                    epi_current, factor, epi_min, epsilon
                )

            # Apply edge-aware EPI scaling with boundary check
            # Edge-aware already computed safe scale, but use unified function
            # for consistency (with apply_clip=True as safety net)
            new_epi = epi_current * scale_eff
            _set_epi_with_boundary_check(node, new_epi, apply_clip=True)

            # Record telemetry if scale was adapted
            if abs(scale_eff - factor) > epsilon:
                telemetry = node.graph.setdefault("edge_aware_interventions", [])
                telemetry.append(
                    {
                        "glyph": glyph.name if hasattr(glyph, "name") else str(glyph),
                        "epi_before": epi_current,
                        "epi_after": float(
                            node.EPI
                        ),  # Get actual value after boundary check
                        "scale_requested": factor,
                        "scale_effective": scale_eff,
                        "adapted": True,
                    }
                )

    _op.__doc__ = """{} glyph scales νf and EPI with edge-aware adaptation.

        VAL (expansion) increases νf and EPI, whereas NUL (contraction) decreases them.
        Edge-aware scaling adapts the scale factor near EPI boundaries to prevent
        overflow/underflow, maintaining structural coherence within [-1.0, 1.0].

        When EDGE_AWARE_ENABLED is True (default), the effective scale is computed as:
        - VAL: scale_eff = min(VAL_scale, EPI_MAX / |EPI_current|)
        - NUL: scale_eff = min(NUL_scale, |EPI_MIN| / |EPI_current|) for negative EPI

        This implements TNFR principle: "resonance to the edge" without breaking
        the structural envelope. Telemetry records adaptation events.

        Parameters
        ----------
        node : NodeProtocol
            Node whose νf and EPI are updated.
        gf : GlyphFactors
            Provides the respective scale factor (``VAL_scale`` or
            ``NUL_scale``).

        Examples
        --------
        >>> class MockNode:
        ...     def __init__(self, vf, epi):
        ...         self.vf = vf
        ...         self.EPI = epi
        ...         self.graph = {{"EDGE_AWARE_ENABLED": True, "EPI_MAX": 1.0}}
        >>> node = MockNode(1.0, 0.96)
        >>> op = _make_scale_op(Glyph.VAL)
        >>> op(node, {{"VAL_scale": 1.05}})
        >>> node.vf  # νf scaled normally
        1.05
        >>> node.EPI <= 1.0  # EPI kept within bounds
        True
        """.format(
        glyph.name
    )
    return _op


def _op_THOL(node: NodeProtocol, gf: GlyphFactors) -> None:  # THOL — Self-organization
    """Inject curvature from ``d2EPI`` into ΔNFR to trigger self-organization.

    The glyph keeps EPI, νf, and phase fixed while increasing ΔNFR according to
    the second derivative of EPI, accelerating structural rearrangement.

    Parameters
    ----------
    node : NodeProtocol
        Node contributing ``d2EPI`` to ΔNFR.
    gf : GlyphFactors
        Source of the ``THOL_accel`` multiplier.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, dnfr, curvature):
    ...         self.dnfr = dnfr
    ...         self.d2EPI = curvature
    >>> node = MockNode(0.1, 0.5)
    >>> _op_THOL(node, {"THOL_accel": 0.2})
    >>> node.dnfr
    0.2
    """
    a = get_factor(gf, "THOL_accel", COUPLING_GENTLE)
    node.dnfr = node.dnfr + a * getattr(node, "d2EPI", 0.0)


def _op_ZHIR(node: NodeProtocol, gf: GlyphFactors) -> None:  # ZHIR — Mutation
    """Apply canonical phase transformation θ → θ' based on structural dynamics.

    ZHIR (Mutation) implements the canonical TNFR phase transformation whose
    DIRECTION and firing are governed by the node's reorganization state (ΔNFR),
    implementing the physics: θ → θ' when ΔEPI/Δt > ξ (AGENTS.md §11,
    TNFR.pdf §2.2.11). Per the operator contract, ZHIR acts on the θ (phase)
    channel; ΔNFR enters only through its SIGN (rotation direction) and the
    bifurcation threshold (whether ZHIR fires, U4b). The shift MAGNITUDE is a
    calibration constant, not a function of |ΔNFR| — only channel and direction
    are canonical (the shift magnitude is an operational calibration value;
    only π is a genuine structural scale).

    **Canonical Behavior**:
    - Direction: Based on ΔNFR sign (positive → forward phase, negative → backward)
    - Magnitude: Calibrated constant theta_shift_factor · (π/4); independent of |ΔNFR|
    - Regime detection: Identifies quadrant crossings (π/2 boundaries)
    - Deterministic: Same seed produces same transformation

    The transformation preserves structural identity (epi_kind) while shifting the
    operational regime, enabling adaptation without losing coherence.

    Parameters
    ----------
    node : NodeProtocol
        Node whose phase is transformed based on its structural state.
    gf : GlyphFactors
        Supplies ``ZHIR_theta_shift_factor`` (default: 0.3) controlling transformation
        magnitude. Can override with explicit ``ZHIR_theta_shift`` for fixed rotation.

    Examples
    --------
    >>> import math
    >>> class MockNode:
    ...     def __init__(self, theta, dnfr):
    ...         self.theta = theta
    ...         self.dnfr = dnfr
    ...         self.graph = {}
    >>> # Positive ΔNFR → forward phase shift
    >>> node = MockNode(0.0, 0.5)
    >>> _op_ZHIR(node, {"ZHIR_theta_shift_factor": 0.3})
    >>> 0.2 < node.theta < 0.3  # ~π/4 * 0.3 ≈ 0.24
    True
    >>> # Negative ΔNFR → backward phase shift
    >>> node2 = MockNode(math.pi, -0.5)
    >>> _op_ZHIR(node2, {"ZHIR_theta_shift_factor": 0.3})
    >>> 2.9 < node2.theta < 3.0  # π - 0.24 ≈ 2.90
    True
    >>> # Fixed shift overrides dynamic behavior
    >>> node3 = MockNode(0.0, 0.5)
    >>> _op_ZHIR(node3, {"ZHIR_theta_shift": math.pi / 2})
    >>> round(node3.theta, 2)
    1.57
    """
    # Check for explicit fixed shift (backward compatibility)
    if "ZHIR_theta_shift" in gf:
        shift = get_factor(gf, "ZHIR_theta_shift", math.pi / 2)
        node.theta = node.theta + shift
        # Store telemetry for fixed shift mode
        storage = node._glyph_storage()
        storage["_zhir_theta_shift"] = shift
        storage["_zhir_fixed_mode"] = True
        return

    # Canonical transformation: θ → θ' based on ΔNFR
    theta_before = node.theta
    dnfr = node.dnfr

    # Transformation magnitude controlled by factor
    theta_shift_factor = get_factor(gf, "ZHIR_theta_shift_factor", INV_PI)

    # Direction based on ΔNFR sign (coherent with structural pressure)
    # Magnitude is a calibration constant (theta_shift_factor · π/4); ΔNFR enters
    # only via its SIGN (direction) and the U4b firing threshold, NOT as |ΔNFR|.
    base_shift = math.pi / 4
    shift = theta_shift_factor * math.copysign(1.0, dnfr) * base_shift

    # Apply transformation with phase wrapping [0, 2π)
    theta_new = (theta_before + shift) % (2 * math.pi)
    node.theta = theta_new

    # Detect regime change (crossing quadrant boundaries)
    regime_before = int(theta_before // (math.pi / 2))
    regime_after = int(theta_new // (math.pi / 2))
    regime_changed = regime_before != regime_after

    # Store telemetry for metrics collection
    storage = node._glyph_storage()
    storage["_zhir_theta_shift"] = shift
    storage["_zhir_theta_before"] = theta_before
    storage["_zhir_theta_after"] = theta_new
    storage["_zhir_regime_changed"] = regime_changed
    storage["_zhir_regime_before"] = regime_before
    storage["_zhir_regime_after"] = regime_after
    storage["_zhir_fixed_mode"] = False


def _op_NAV(node: NodeProtocol, gf: GlyphFactors) -> None:  # NAV — Transition
    """Rebalance ΔNFR towards νf while permitting jitter.

    Transition pulls ΔNFR towards a νf-aligned target, optionally adding jitter
    to explore nearby states. EPI and phase remain untouched; νf may be used as
    a reference but is not directly changed.

    Parameters
    ----------
    node : NodeProtocol
        Node whose ΔNFR is redirected.
    gf : GlyphFactors
        Supplies ``NAV_eta`` and ``NAV_jitter`` tuning parameters.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self, dnfr, vf):
    ...         self.dnfr = dnfr
    ...         self.vf = vf
    ...         self.graph = {"NAV_RANDOM": False}
    >>> node = MockNode(-0.6, 0.4)
    >>> _op_NAV(node, {"NAV_eta": 0.5, "NAV_jitter": 0.0})
    >>> round(node.dnfr, 2)
    -0.1
    """
    dnfr = node.dnfr
    vf = node.vf
    eta = get_factor(gf, "NAV_eta", 0.5)
    strict = bool(node.graph.get("NAV_STRICT", False))
    if strict:
        base = vf
    else:
        sign = 1.0 if dnfr >= 0 else -1.0
        target = sign * vf
        base = (1.0 - eta) * dnfr + eta * target
    j = get_factor(gf, "NAV_jitter", COUPLING_FINE)
    if bool(node.graph.get("NAV_RANDOM", True)):
        jitter = random_jitter(node, j)
    else:
        jitter = j * (1 if base >= 0 else -1)
    node.dnfr = base + jitter


def _op_REMESH(
    node: NodeProtocol, gf: GlyphFactors | None = None
) -> None:  # REMESH — advisory
    """Record an advisory requesting network-scale remeshing.

    REMESH does not change node-level EPI, νf, ΔNFR, or phase. Instead it
    annotates the glyph history so orchestrators can trigger global remesh
    procedures once the stability conditions are met.

    Parameters
    ----------
    node : NodeProtocol
        Node whose history records the advisory.
    gf : GlyphFactors, optional
        Unused but accepted for API symmetry.

    Examples
    --------
    >>> class MockNode:
    ...     def __init__(self):
    ...         self.graph = {}
    >>> node = MockNode()
    >>> _op_REMESH(node)
    >>> "_remesh_warn_step" in node.graph
    True
    """
    step_idx = glyph_history.current_step_idx(node)
    last_warn = node.graph.get("_remesh_warn_step", None)
    if last_warn != step_idx:
        msg = (
            "REMESH operates at network scale. Use apply_remesh_if_globally_"
            "stable(G) or apply_network_remesh(G)."
        )
        hist = glyph_history.ensure_history(node)
        glyph_history.append_metric(
            hist,
            "events",
            ("warn", {"step": step_idx, "node": None, "msg": msg}),
        )
        node.graph["_remesh_warn_step"] = step_idx
    return


# -------------------------
# Dispatcher
# -------------------------

GLYPH_OPERATIONS: dict[Glyph, GlyphOperation] = {
    Glyph.AL: _op_AL,
    Glyph.EN: _op_EN,
    Glyph.IL: _op_IL,
    Glyph.OZ: _op_OZ,
    Glyph.UM: _op_UM,
    Glyph.RA: _op_RA,
    Glyph.SHA: _op_SHA,
    Glyph.VAL: _make_scale_op(Glyph.VAL),
    Glyph.NUL: _make_scale_op(Glyph.NUL),
    Glyph.THOL: _op_THOL,
    Glyph.ZHIR: _op_ZHIR,
    Glyph.NAV: _op_NAV,
    Glyph.REMESH: _op_REMESH,
}


def apply_glyph_obj(
    node: NodeProtocol, glyph: Glyph | str, *, window: int | None = None
) -> None:
    """Apply ``glyph`` to an object satisfying :class:`NodeProtocol`."""

    from ..validation.input_validation import ValidationError, validate_glyph
    from .grammar import function_name_to_glyph

    # Validate glyph parameter
    try:
        if not isinstance(glyph, Glyph):
            validated_glyph = validate_glyph(glyph)
            glyph = (
                validated_glyph.value
                if isinstance(validated_glyph, Glyph)
                else str(glyph)
            )
        else:
            glyph = glyph.value
    except ValidationError as e:
        step_idx = glyph_history.current_step_idx(node)
        hist = glyph_history.ensure_history(node)
        glyph_history.append_metric(
            hist,
            "events",
            (
                "warn",
                {
                    "step": step_idx,
                    "node": getattr(node, "n", None),
                    "msg": f"invalid glyph: {e}",
                },
            ),
        )
        raise TNFRValueError(f"invalid glyph: {e}", context={"error": str(e)}) from e

    # Try direct glyph code first
    try:
        g = Glyph(str(glyph))
    except ValueError:
        # Try structural function name mapping
        g = function_name_to_glyph(glyph)
        if g is None:
            step_idx = glyph_history.current_step_idx(node)
            hist = glyph_history.ensure_history(node)
            glyph_history.append_metric(
                hist,
                "events",
                (
                    "warn",
                    {
                        "step": step_idx,
                        "node": getattr(node, "n", None),
                        "msg": f"unknown glyph: {glyph}",
                    },
                ),
            )
            raise TNFRValueError(f"unknown glyph: {glyph}", context={"glyph": glyph})

    op = GLYPH_OPERATIONS.get(g)
    if op is None:
        raise TNFRValueError(
            f"glyph has no registered operator: {g}", context={"glyph": g}
        )
    if window is None:
        window = int(get_param(node, "GLYPH_HYSTERESIS_WINDOW"))
    gf = get_glyph_factors(node)
    op(node, gf)
    glyph_history.push_glyph(node._glyph_storage(), g.value, window)
    node.epi_kind = g.value


def apply_glyph(
    G: TNFRGraph, n: NodeId, glyph: Glyph | str, *, window: int | None = None
) -> None:
    """Adapter to operate on ``networkx`` graphs."""
    from ..validation.input_validation import (
        ValidationError,
        validate_node_id,
        validate_tnfr_graph,
    )

    # Validate graph and node parameters
    try:
        validate_tnfr_graph(G)
        validate_node_id(n)
    except ValidationError as e:
        raise TNFRValueError(
            f"Invalid parameters for apply_glyph: {e}", context={"error": str(e)}
        ) from e

    NodeNX = get_nodenx()
    if NodeNX is None:
        raise ImportError("NodeNX is unavailable")
    node = NodeNX(G, n)
    apply_glyph_obj(node, glyph, window=window)