tnfr 4.0.0__py3-none-any.whl → 4.3.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.
- tnfr/__init__.py +54 -50
- tnfr/cli.py +94 -1
- tnfr/constants.py +30 -23
- tnfr/dynamics.py +99 -33
- tnfr/gamma.py +28 -9
- tnfr/grammar.py +6 -0
- tnfr/helpers.py +27 -1
- tnfr/metrics.py +50 -27
- tnfr/operators.py +59 -20
- tnfr/presets.py +5 -1
- tnfr/program.py +8 -0
- tnfr/scenarios.py +9 -3
- tnfr/sense.py +6 -21
- tnfr/trace.py +15 -26
- {tnfr-4.0.0.dist-info → tnfr-4.3.0.dist-info}/METADATA +12 -4
- tnfr-4.3.0.dist-info/RECORD +24 -0
- tnfr-4.0.0.dist-info/RECORD +0 -24
- {tnfr-4.0.0.dist-info → tnfr-4.3.0.dist-info}/WHEEL +0 -0
- {tnfr-4.0.0.dist-info → tnfr-4.3.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.0.0.dist-info → tnfr-4.3.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.0.0.dist-info → tnfr-4.3.0.dist-info}/top_level.txt +0 -0
tnfr/__init__.py
CHANGED
|
@@ -1,57 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
"""
|
|
4
|
-
TNFR — Teoría de la Naturaleza Fractal Resonante
|
|
5
|
-
API pública del paquete.
|
|
6
|
-
|
|
7
|
-
Ecuación nodal:
|
|
8
|
-
∂EPI/∂t = νf · ΔNFR(t)
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
__version__ = "4.
|
|
12
|
-
|
|
13
|
-
# Re-exports de la API pública
|
|
14
|
-
from .dynamics import step, run, set_delta_nfr_hook
|
|
15
|
-
from .ontosim import preparar_red
|
|
16
|
-
from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
|
|
17
|
-
from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
|
|
18
|
-
from .grammar import enforce_canonical_grammar, on_applied_glifo
|
|
19
|
-
from .sense import (
|
|
20
|
-
GLYPHS_CANONICAL, glyph_angle, glyph_unit,
|
|
21
|
-
sigma_vector_node, sigma_vector_global,
|
|
22
|
-
push_sigma_snapshot, sigma_series, sigma_rose,
|
|
23
|
-
register_sigma_callback,
|
|
24
|
-
)
|
|
1
|
+
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
"""
|
|
4
|
+
TNFR — Teoría de la Naturaleza Fractal Resonante
|
|
5
|
+
API pública del paquete.
|
|
6
|
+
|
|
7
|
+
Ecuación nodal:
|
|
8
|
+
∂EPI/∂t = νf · ΔNFR(t)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "4.3.0"
|
|
12
|
+
|
|
13
|
+
# Re-exports de la API pública
|
|
14
|
+
from .dynamics import step, run, set_delta_nfr_hook, validate_canon
|
|
15
|
+
from .ontosim import preparar_red
|
|
16
|
+
from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
|
|
17
|
+
from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
|
|
18
|
+
from .grammar import enforce_canonical_grammar, on_applied_glifo
|
|
19
|
+
from .sense import (
|
|
20
|
+
GLYPHS_CANONICAL, glyph_angle, glyph_unit,
|
|
21
|
+
sigma_vector_node, sigma_vector_global,
|
|
22
|
+
push_sigma_snapshot, sigma_series, sigma_rose,
|
|
23
|
+
register_sigma_callback,
|
|
24
|
+
)
|
|
25
25
|
from .metrics import (
|
|
26
26
|
register_metrics_callbacks,
|
|
27
27
|
Tg_global, Tg_by_node,
|
|
28
28
|
latency_series, glifogram_series,
|
|
29
|
-
glyph_top, glyph_dwell_stats,
|
|
29
|
+
glyph_top, glyph_dwell_stats, export_history,
|
|
30
30
|
)
|
|
31
|
-
from .trace import register_trace
|
|
32
|
-
from .program import play, seq, block, target, wait, THOL, TARGET, WAIT
|
|
33
|
-
from .cli import main as cli_main
|
|
34
|
-
from .scenarios import build_graph
|
|
35
|
-
from .presets import get_preset
|
|
36
|
-
from .types import NodeState
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
from .trace import register_trace
|
|
32
|
+
from .program import play, seq, block, target, wait, THOL, TARGET, WAIT, ejemplo_canonico_basico
|
|
33
|
+
from .cli import main as cli_main
|
|
34
|
+
from .scenarios import build_graph
|
|
35
|
+
from .presets import get_preset
|
|
36
|
+
from .types import NodeState
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
39
40
|
"preparar_red",
|
|
40
|
-
"step", "run", "set_delta_nfr_hook",
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
41
|
+
"step", "run", "set_delta_nfr_hook", "validate_canon",
|
|
42
|
+
|
|
43
|
+
"attach_standard_observer", "coherencia_global", "orden_kuramoto",
|
|
44
|
+
"GAMMA_REGISTRY", "eval_gamma", "kuramoto_R_psi",
|
|
45
|
+
"enforce_canonical_grammar", "on_applied_glifo",
|
|
46
|
+
"GLYPHS_CANONICAL", "glyph_angle", "glyph_unit",
|
|
47
|
+
"sigma_vector_node", "sigma_vector_global",
|
|
48
|
+
"push_sigma_snapshot", "sigma_series", "sigma_rose",
|
|
49
|
+
"register_sigma_callback",
|
|
50
|
+
"register_metrics_callbacks",
|
|
51
|
+
"register_trace",
|
|
52
|
+
"Tg_global", "Tg_by_node",
|
|
53
|
+
"latency_series", "glifogram_series",
|
|
52
54
|
"glyph_top", "glyph_dwell_stats",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
"export_history",
|
|
56
|
+
"play", "seq", "block", "target", "wait", "THOL", "TARGET", "WAIT",
|
|
57
|
+
"cli_main", "build_graph", "get_preset", "NodeState",
|
|
58
|
+
"ejemplo_canonico_basico",
|
|
59
|
+
"__version__",
|
|
60
|
+
]
|
|
61
|
+
|
tnfr/cli.py
CHANGED
|
@@ -18,10 +18,12 @@ from .metrics import (
|
|
|
18
18
|
latency_series,
|
|
19
19
|
glifogram_series,
|
|
20
20
|
glyph_top,
|
|
21
|
+
export_history,
|
|
21
22
|
)
|
|
22
23
|
from .trace import register_trace
|
|
23
24
|
from .program import play, seq, block, wait, target
|
|
24
|
-
from .dynamics import step, _update_history
|
|
25
|
+
from .dynamics import step, _update_history, default_glyph_selector, parametric_glyph_selector, validate_canon
|
|
26
|
+
from .gamma import GAMMA_REGISTRY
|
|
25
27
|
from .scenarios import build_graph
|
|
26
28
|
from .presets import get_preset
|
|
27
29
|
|
|
@@ -31,6 +33,24 @@ def _save_json(path: str, data: Any) -> None:
|
|
|
31
33
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def _str2bool(s: str) -> bool:
|
|
37
|
+
s = s.lower()
|
|
38
|
+
if s in {"true", "1", "yes", "y"}:
|
|
39
|
+
return True
|
|
40
|
+
if s in {"false", "0", "no", "n"}:
|
|
41
|
+
return False
|
|
42
|
+
raise argparse.ArgumentTypeError("expected true/false")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _args_to_dict(args: argparse.Namespace, prefix: str) -> Dict[str, Any]:
|
|
46
|
+
out: Dict[str, Any] = {}
|
|
47
|
+
pref = prefix.replace(".", "_")
|
|
48
|
+
for k, v in vars(args).items():
|
|
49
|
+
if k.startswith(pref) and v is not None:
|
|
50
|
+
out[k[len(pref):]] = v
|
|
51
|
+
return out
|
|
52
|
+
|
|
53
|
+
|
|
34
54
|
def _load_sequence(path: str) -> List[Any]:
|
|
35
55
|
with open(path, "r", encoding="utf-8") as f:
|
|
36
56
|
text = f.read()
|
|
@@ -72,6 +92,20 @@ def _attach_callbacks(G: nx.Graph) -> None:
|
|
|
72
92
|
def cmd_run(args: argparse.Namespace) -> int:
|
|
73
93
|
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
74
94
|
_attach_callbacks(G)
|
|
95
|
+
validate_canon(G)
|
|
96
|
+
gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
|
|
97
|
+
gcanon.update(_args_to_dict(args, prefix="grammar."))
|
|
98
|
+
if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
|
|
99
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
100
|
+
G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
|
|
101
|
+
if args.glyph_hysteresis_window is not None:
|
|
102
|
+
G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
|
|
103
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
104
|
+
G.graph["GAMMA"] = {
|
|
105
|
+
"type": args.gamma_type,
|
|
106
|
+
"beta": args.gamma_beta,
|
|
107
|
+
"R0": args.gamma_R0,
|
|
108
|
+
}
|
|
75
109
|
|
|
76
110
|
if args.preset:
|
|
77
111
|
program = get_preset(args.preset)
|
|
@@ -83,6 +117,8 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
83
117
|
|
|
84
118
|
if args.save_history:
|
|
85
119
|
_save_json(args.save_history, G.graph.get("history", {}))
|
|
120
|
+
if args.export_history_base:
|
|
121
|
+
export_history(G, args.export_history_base, fmt=args.export_format)
|
|
86
122
|
|
|
87
123
|
if args.summary:
|
|
88
124
|
tg = Tg_global(G, normalize=True)
|
|
@@ -96,6 +132,20 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
|
96
132
|
def cmd_sequence(args: argparse.Namespace) -> int:
|
|
97
133
|
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
98
134
|
_attach_callbacks(G)
|
|
135
|
+
validate_canon(G)
|
|
136
|
+
gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
|
|
137
|
+
gcanon.update(_args_to_dict(args, prefix="grammar."))
|
|
138
|
+
if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
|
|
139
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
140
|
+
G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
|
|
141
|
+
if args.glyph_hysteresis_window is not None:
|
|
142
|
+
G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
|
|
143
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
144
|
+
G.graph["GAMMA"] = {
|
|
145
|
+
"type": args.gamma_type,
|
|
146
|
+
"beta": args.gamma_beta,
|
|
147
|
+
"R0": args.gamma_R0,
|
|
148
|
+
}
|
|
99
149
|
|
|
100
150
|
if args.preset:
|
|
101
151
|
program = get_preset(args.preset)
|
|
@@ -108,12 +158,22 @@ def cmd_sequence(args: argparse.Namespace) -> int:
|
|
|
108
158
|
|
|
109
159
|
if args.save_history:
|
|
110
160
|
_save_json(args.save_history, G.graph.get("history", {}))
|
|
161
|
+
if args.export_history_base:
|
|
162
|
+
export_history(G, args.export_history_base, fmt=args.export_format)
|
|
111
163
|
return 0
|
|
112
164
|
|
|
113
165
|
|
|
114
166
|
def cmd_metrics(args: argparse.Namespace) -> int:
|
|
115
167
|
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
116
168
|
_attach_callbacks(G)
|
|
169
|
+
validate_canon(G)
|
|
170
|
+
G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
|
|
171
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
172
|
+
G.graph["GAMMA"] = {
|
|
173
|
+
"type": args.gamma_type,
|
|
174
|
+
"beta": args.gamma_beta,
|
|
175
|
+
"R0": args.gamma_R0,
|
|
176
|
+
}
|
|
117
177
|
for _ in range(int(args.steps or 200)):
|
|
118
178
|
step(G)
|
|
119
179
|
|
|
@@ -146,7 +206,22 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
146
206
|
p_run.add_argument("--seed", type=int, default=1)
|
|
147
207
|
p_run.add_argument("--preset", type=str, default=None)
|
|
148
208
|
p_run.add_argument("--save-history", dest="save_history", type=str, default=None)
|
|
209
|
+
p_run.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
|
|
210
|
+
p_run.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
|
|
149
211
|
p_run.add_argument("--summary", action="store_true")
|
|
212
|
+
p_run.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
|
|
213
|
+
p_run.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
|
|
214
|
+
p_run.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
|
|
215
|
+
p_run.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
|
|
216
|
+
p_run.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
|
|
217
|
+
p_run.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
|
|
218
|
+
p_run.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
|
|
219
|
+
p_run.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
|
|
220
|
+
p_run.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
|
|
221
|
+
p_run.add_argument("--selector", choices=["basic", "param"], default="basic")
|
|
222
|
+
p_run.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
223
|
+
p_run.add_argument("--gamma-beta", type=float, default=0.0)
|
|
224
|
+
p_run.add_argument("--gamma-R0", type=float, default=0.0)
|
|
150
225
|
p_run.set_defaults(func=cmd_run)
|
|
151
226
|
|
|
152
227
|
p_seq = sub.add_parser("sequence", help="Ejecutar una secuencia (preset o YAML/JSON)")
|
|
@@ -156,6 +231,19 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
156
231
|
p_seq.add_argument("--preset", type=str, default=None)
|
|
157
232
|
p_seq.add_argument("--sequence-file", type=str, default=None)
|
|
158
233
|
p_seq.add_argument("--save-history", dest="save_history", type=str, default=None)
|
|
234
|
+
p_seq.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
|
|
235
|
+
p_seq.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
|
|
236
|
+
p_seq.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
237
|
+
p_seq.add_argument("--gamma-beta", type=float, default=0.0)
|
|
238
|
+
p_seq.add_argument("--gamma-R0", type=float, default=0.0)
|
|
239
|
+
p_seq.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
|
|
240
|
+
p_seq.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
|
|
241
|
+
p_seq.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
|
|
242
|
+
p_seq.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
|
|
243
|
+
p_seq.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
|
|
244
|
+
p_seq.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
|
|
245
|
+
p_seq.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
|
|
246
|
+
p_seq.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
|
|
159
247
|
p_seq.set_defaults(func=cmd_sequence)
|
|
160
248
|
|
|
161
249
|
p_met = sub.add_parser("metrics", help="Correr breve y volcar métricas clave")
|
|
@@ -163,6 +251,11 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
163
251
|
p_met.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
|
|
164
252
|
p_met.add_argument("--steps", type=int, default=300)
|
|
165
253
|
p_met.add_argument("--seed", type=int, default=1)
|
|
254
|
+
p_met.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
|
|
255
|
+
p_met.add_argument("--selector", choices=["basic", "param"], default="basic")
|
|
256
|
+
p_met.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
257
|
+
p_met.add_argument("--gamma-beta", type=float, default=0.0)
|
|
258
|
+
p_met.add_argument("--gamma-R0", type=float, default=0.0)
|
|
166
259
|
p_met.add_argument("--save", type=str, default=None)
|
|
167
260
|
p_met.set_defaults(func=cmd_metrics)
|
|
168
261
|
|
tnfr/constants.py
CHANGED
|
@@ -72,18 +72,25 @@ DEFAULTS: Dict[str, Any] = {
|
|
|
72
72
|
# Criterios de estabilidad (para activar RE’MESH de red)
|
|
73
73
|
"EPS_DNFR_STABLE": 1e-3,
|
|
74
74
|
"EPS_DEPI_STABLE": 1e-3,
|
|
75
|
-
"FRACTION_STABLE_REMESH": 0.80, # fracción de nodos estables requerida
|
|
76
|
-
"REMESH_COOLDOWN_VENTANA": 20, # pasos mínimos entre RE’MESH
|
|
75
|
+
"FRACTION_STABLE_REMESH": 0.80, # fracción de nodos estables requerida
|
|
76
|
+
"REMESH_COOLDOWN_VENTANA": 20, # pasos mínimos entre RE’MESH
|
|
77
|
+
"REMESH_COOLDOWN_TS": 0.0, # cooldown adicional por tiempo simulado
|
|
77
78
|
# Gating adicional basado en observadores (conmutador + ventana)
|
|
78
|
-
"REMESH_REQUIRE_STABILITY": False, # si True, exige ventana de estabilidad multi-métrica
|
|
79
|
-
"REMESH_STABILITY_WINDOW": 25, # tamaño de ventana para evaluar estabilidad
|
|
80
|
-
"REMESH_MIN_PHASE_SYNC": 0.85, # media mínima de sincronía de fase en ventana
|
|
81
|
-
"REMESH_MAX_GLYPH_DISR": 0.35, # media máxima de carga glífica disruptiva en ventana
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
#
|
|
85
|
-
"
|
|
86
|
-
|
|
79
|
+
"REMESH_REQUIRE_STABILITY": False, # si True, exige ventana de estabilidad multi-métrica
|
|
80
|
+
"REMESH_STABILITY_WINDOW": 25, # tamaño de ventana para evaluar estabilidad
|
|
81
|
+
"REMESH_MIN_PHASE_SYNC": 0.85, # media mínima de sincronía de fase en ventana
|
|
82
|
+
"REMESH_MAX_GLYPH_DISR": 0.35, # media máxima de carga glífica disruptiva en ventana
|
|
83
|
+
"REMESH_MIN_SIGMA_MAG": 0.50, # magnitud mínima de σ en ventana
|
|
84
|
+
"REMESH_MIN_KURAMOTO_R": 0.80, # R de Kuramoto mínimo en ventana
|
|
85
|
+
"REMESH_MIN_SI_HI_FRAC": 0.50, # fracción mínima de nodos con Si alto
|
|
86
|
+
"REMESH_LOG_EVENTS": True, # guarda eventos y metadatos del RE’MESH
|
|
87
|
+
|
|
88
|
+
# RE’MESH: memoria τ y mezcla α (global/local)
|
|
89
|
+
"REMESH_TAU": 8, # compatibilidad: tau global por defecto
|
|
90
|
+
"REMESH_TAU_GLOBAL": 8, # pasos hacia atrás (escala global)
|
|
91
|
+
"REMESH_TAU_LOCAL": 4, # pasos hacia atrás (escala local)
|
|
92
|
+
"REMESH_ALPHA": 0.5, # mezcla con pasado
|
|
93
|
+
"REMESH_ALPHA_HARD": False, # si True ignora GLYPH_FACTORS['REMESH_alpha']
|
|
87
94
|
|
|
88
95
|
# Histéresis glífica
|
|
89
96
|
"GLYPH_HYSTERESIS_WINDOW": 7,
|
|
@@ -92,13 +99,13 @@ DEFAULTS: Dict[str, Any] = {
|
|
|
92
99
|
"GLYPH_SELECTOR_MARGIN": 0.05,
|
|
93
100
|
|
|
94
101
|
# Ventana para estimar la carga glífica en history/plots
|
|
95
|
-
"GLYPH_LOAD_WINDOW": 50,
|
|
102
|
+
"GLYPH_LOAD_WINDOW": 50,
|
|
96
103
|
|
|
97
104
|
# Tamaño de ventana para coherencia promedio W̄
|
|
98
105
|
"WBAR_WINDOW": 25,
|
|
99
106
|
|
|
100
107
|
# Factores suaves por glifo (operadores)
|
|
101
|
-
"GLYPH_FACTORS": {
|
|
108
|
+
"GLYPH_FACTORS": {
|
|
102
109
|
"AL_boost": 0.05, # A’L — pequeña emisión
|
|
103
110
|
"EN_mix": 0.25, # E’N — mezcla con vecindad
|
|
104
111
|
"IL_dnfr_factor": 0.7, # I’L — reduce ΔNFR
|
|
@@ -107,7 +114,7 @@ DEFAULTS: Dict[str, Any] = {
|
|
|
107
114
|
"RA_epi_diff": 0.15, # R’A — difusión EPI
|
|
108
115
|
"SHA_vf_factor": 0.85, # SH’A — baja νf
|
|
109
116
|
"VAL_scale": 1.15, # VA’L — expande EPI
|
|
110
|
-
"NUL_scale": 0.85, # NU’L — contrae EPI
|
|
117
|
+
"NUL_scale": 0.85, # NU’L — contrae EPI
|
|
111
118
|
"THOL_accel": 0.10, # T’HOL — acelera (seg. deriv.) si hay umbral
|
|
112
119
|
"ZHIR_theta_shift": 1.57079632679, # Z’HIR — desplazamiento ~π/2
|
|
113
120
|
"NAV_jitter": 0.05, # NA’V — pequeña inestabilidad creativa
|
|
@@ -154,6 +161,7 @@ DEFAULTS: Dict[str, Any] = {
|
|
|
154
161
|
"R0": 0.0,
|
|
155
162
|
},
|
|
156
163
|
"CALLBACKS_STRICT": False, # si True, un error en callback detiene; si False, se loguea y continúa
|
|
164
|
+
"VALIDATORS_STRICT": False, # si True, alerta si se clampa fuera de rango
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
# Gramática glífica canónica
|
|
@@ -174,11 +182,7 @@ DEFAULTS.setdefault("GRAMMAR_CANON", {
|
|
|
174
182
|
|
|
175
183
|
def attach_defaults(G, override: bool = False) -> None:
|
|
176
184
|
"""Escribe DEFAULTS en G.graph (sin sobreescribir si override=False)."""
|
|
177
|
-
G
|
|
178
|
-
for k, v in DEFAULTS.items():
|
|
179
|
-
if override or k not in G.graph:
|
|
180
|
-
G.graph[k] = v
|
|
181
|
-
G.graph["_tnfr_defaults_attached"] = True
|
|
185
|
+
inject_defaults(G, DEFAULTS, override=override)
|
|
182
186
|
|
|
183
187
|
|
|
184
188
|
def inject_defaults(G, defaults: Dict[str, Any] = DEFAULTS, override: bool = False) -> None:
|
|
@@ -207,7 +211,10 @@ def merge_overrides(G, **overrides) -> None:
|
|
|
207
211
|
ALIAS_VF = ("νf", "nu_f", "nu-f", "nu", "freq", "frequency")
|
|
208
212
|
ALIAS_THETA = ("θ", "theta", "fase", "phi", "phase")
|
|
209
213
|
ALIAS_DNFR = ("ΔNFR", "delta_nfr", "dnfr")
|
|
210
|
-
ALIAS_EPI = ("EPI", "psi", "PSI", "value")
|
|
211
|
-
ALIAS_SI = ("Si", "sense_index", "S_i", "sense", "meaning_index")
|
|
212
|
-
ALIAS_dEPI = ("dEPI_dt", "dpsi_dt", "dEPI", "velocity")
|
|
213
|
-
ALIAS_D2EPI = ("d2EPI_dt2", "d2psi_dt2", "d2EPI", "accel")
|
|
214
|
+
ALIAS_EPI = ("EPI", "psi", "PSI", "value")
|
|
215
|
+
ALIAS_SI = ("Si", "sense_index", "S_i", "sense", "meaning_index")
|
|
216
|
+
ALIAS_dEPI = ("dEPI_dt", "dpsi_dt", "dEPI", "velocity")
|
|
217
|
+
ALIAS_D2EPI = ("d2EPI_dt2", "d2psi_dt2", "d2EPI", "accel")
|
|
218
|
+
ALIAS_dVF = ("dνf_dt", "dvf_dt", "dnu_dt", "dvf")
|
|
219
|
+
ALIAS_D2VF = ("d2νf_dt2", "d2vf_dt2", "d2nu_dt2", "B")
|
|
220
|
+
ALIAS_dSI = ("δSi", "delta_Si", "dSi")
|
tnfr/dynamics.py
CHANGED
|
@@ -19,7 +19,11 @@ import networkx as nx
|
|
|
19
19
|
from .observers import sincronía_fase, carga_glifica, orden_kuramoto, sigma_vector
|
|
20
20
|
from .operators import aplicar_remesh_si_estabilizacion_global
|
|
21
21
|
from .grammar import select_and_apply_with_grammar
|
|
22
|
-
from .constants import
|
|
22
|
+
from .constants import (
|
|
23
|
+
DEFAULTS,
|
|
24
|
+
ALIAS_VF, ALIAS_THETA, ALIAS_DNFR, ALIAS_EPI, ALIAS_SI,
|
|
25
|
+
ALIAS_dEPI, ALIAS_D2EPI, ALIAS_dVF, ALIAS_D2VF, ALIAS_dSI,
|
|
26
|
+
)
|
|
23
27
|
from .gamma import eval_gamma
|
|
24
28
|
from .helpers import (
|
|
25
29
|
clamp, clamp01, list_mean, phase_distance,
|
|
@@ -187,7 +191,7 @@ def integrar_epi_euler(G, dt: float | None = None) -> None:
|
|
|
187
191
|
update_epi_via_nodal_equation(G, dt=dt)
|
|
188
192
|
|
|
189
193
|
|
|
190
|
-
def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
|
|
194
|
+
def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None, node=None) -> None:
|
|
191
195
|
eps_min = float((G.graph.get("EPI_MIN") if G is not None else DEFAULTS["EPI_MIN"]))
|
|
192
196
|
eps_max = float((G.graph.get("EPI_MAX") if G is not None else DEFAULTS["EPI_MAX"]))
|
|
193
197
|
vf_min = float((G.graph.get("VF_MIN") if G is not None else DEFAULTS["VF_MIN"]))
|
|
@@ -197,6 +201,14 @@ def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
|
|
|
197
201
|
vf = _get_attr(nd, ALIAS_VF, 0.0)
|
|
198
202
|
th = _get_attr(nd, ALIAS_THETA, 0.0)
|
|
199
203
|
|
|
204
|
+
strict = bool((G.graph.get("VALIDATORS_STRICT") if G is not None else DEFAULTS.get("VALIDATORS_STRICT", False)))
|
|
205
|
+
if strict and G is not None:
|
|
206
|
+
hist = G.graph.setdefault("history", {}).setdefault("clamp_alerts", [])
|
|
207
|
+
if epi < eps_min or epi > eps_max:
|
|
208
|
+
hist.append({"node": node, "attr": "EPI", "value": float(epi)})
|
|
209
|
+
if vf < vf_min or vf > vf_max:
|
|
210
|
+
hist.append({"node": node, "attr": "VF", "value": float(vf)})
|
|
211
|
+
|
|
200
212
|
_set_attr(nd, ALIAS_EPI, clamp(epi, eps_min, eps_max))
|
|
201
213
|
_set_attr(nd, ALIAS_VF, clamp(vf, vf_min, vf_max))
|
|
202
214
|
if (G.graph.get("THETA_WRAP") if G is not None else DEFAULTS["THETA_WRAP"]):
|
|
@@ -204,6 +216,17 @@ def aplicar_clamps_canonicos(nd: Dict[str, Any], G=None) -> None:
|
|
|
204
216
|
_set_attr(nd, ALIAS_THETA, ((th + math.pi) % (2*math.pi) - math.pi))
|
|
205
217
|
|
|
206
218
|
|
|
219
|
+
def validate_canon(G) -> None:
|
|
220
|
+
"""Aplica clamps canónicos a todos los nodos de ``G``.
|
|
221
|
+
|
|
222
|
+
Envuelve fase y restringe ``EPI`` y ``νf`` a los rangos en ``G.graph``.
|
|
223
|
+
Si ``VALIDATORS_STRICT`` está activo, registra alertas en ``history``.
|
|
224
|
+
"""
|
|
225
|
+
for n in G.nodes():
|
|
226
|
+
aplicar_clamps_canonicos(G.nodes[n], G, n)
|
|
227
|
+
return G
|
|
228
|
+
|
|
229
|
+
|
|
207
230
|
def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_vecinal: float | None = None) -> None:
|
|
208
231
|
"""
|
|
209
232
|
Ajusta fase con mezcla GLOBAL+VECINAL.
|
|
@@ -212,6 +235,10 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
|
|
|
212
235
|
"""
|
|
213
236
|
g = G.graph
|
|
214
237
|
defaults = DEFAULTS
|
|
238
|
+
hist = g.setdefault("history", {})
|
|
239
|
+
hist_state = hist.setdefault("phase_state", [])
|
|
240
|
+
hist_R = hist.setdefault("phase_R", [])
|
|
241
|
+
hist_disr = hist.setdefault("phase_disr", [])
|
|
215
242
|
# 0) Si hay fuerzas explícitas, usar y salir del modo adaptativo
|
|
216
243
|
if (fuerza_global is not None) or (fuerza_vecinal is not None):
|
|
217
244
|
kG = float(
|
|
@@ -273,20 +300,15 @@ def coordinar_fase_global_vecinal(G, fuerza_global: float | None = None, fuerza_
|
|
|
273
300
|
kL = _step(kL, kL_t, kL_min, kL_max)
|
|
274
301
|
|
|
275
302
|
# 5) Persistir en G.graph y log de serie
|
|
276
|
-
g["PHASE_K_GLOBAL"] = kG
|
|
277
|
-
g["PHASE_K_LOCAL"] = kL
|
|
278
|
-
hist = g.setdefault("history", {})
|
|
279
|
-
hist_kG = hist.setdefault("phase_kG", [])
|
|
280
|
-
hist_kL = hist.setdefault("phase_kL", [])
|
|
281
|
-
hist_state = hist.setdefault("phase_state", [])
|
|
282
|
-
hist_R = hist.setdefault("phase_R", [])
|
|
283
|
-
hist_disr = hist.setdefault("phase_disr", [])
|
|
284
|
-
hist_kG.append(float(kG))
|
|
285
|
-
hist_kL.append(float(kL))
|
|
286
303
|
hist_state.append(state)
|
|
287
304
|
hist_R.append(float(R))
|
|
288
305
|
hist_disr.append(float(disr))
|
|
289
306
|
|
|
307
|
+
g["PHASE_K_GLOBAL"] = kG
|
|
308
|
+
g["PHASE_K_LOCAL"] = kL
|
|
309
|
+
hist.setdefault("phase_kG", []).append(float(kG))
|
|
310
|
+
hist.setdefault("phase_kL", []).append(float(kL))
|
|
311
|
+
|
|
290
312
|
# 6) Fase GLOBAL (centroide) para empuje
|
|
291
313
|
X = list(math.cos(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
|
|
292
314
|
Y = list(math.sin(_get_attr(G.nodes[n], ALIAS_THETA, 0.0)) for n in G.nodes())
|
|
@@ -345,6 +367,21 @@ def _norms_para_selector(G) -> dict:
|
|
|
345
367
|
G.graph["_sel_norms"] = norms
|
|
346
368
|
return norms
|
|
347
369
|
|
|
370
|
+
|
|
371
|
+
def _soft_grammar_prefilter(G, n, cand, dnfr, accel):
|
|
372
|
+
"""Gramática suave: evita repeticiones antes de la canónica."""
|
|
373
|
+
gram = G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {}))
|
|
374
|
+
gwin = int(gram.get("window", 3))
|
|
375
|
+
avoid = set(gram.get("avoid_repeats", []))
|
|
376
|
+
force_dn = float(gram.get("force_dnfr", 0.60))
|
|
377
|
+
force_ac = float(gram.get("force_accel", 0.60))
|
|
378
|
+
fallbacks = gram.get("fallbacks", {})
|
|
379
|
+
nd = G.nodes[n]
|
|
380
|
+
if cand in avoid and reciente_glifo(nd, cand, gwin):
|
|
381
|
+
if not (dnfr >= force_dn or accel >= force_ac):
|
|
382
|
+
cand = fallbacks.get(cand, cand)
|
|
383
|
+
return cand
|
|
384
|
+
|
|
348
385
|
def parametric_glyph_selector(G, n) -> str:
|
|
349
386
|
"""Multiobjetivo: combina Si, |ΔNFR|_norm y |accel|_norm + histéresis.
|
|
350
387
|
Reglas base:
|
|
@@ -403,6 +440,19 @@ def parametric_glyph_selector(G, n) -> str:
|
|
|
403
440
|
prev = list(hist)[-1]
|
|
404
441
|
if isinstance(prev, str) and prev in ("I’L","O’Z","Z’HIR","T’HOL","NA’V","R’A"):
|
|
405
442
|
return prev
|
|
443
|
+
|
|
444
|
+
# Penalización por falta de avance en σ/Si si se repite glifo
|
|
445
|
+
prev = None
|
|
446
|
+
hist_prev = nd.get("hist_glifos")
|
|
447
|
+
if hist_prev:
|
|
448
|
+
prev = list(hist_prev)[-1]
|
|
449
|
+
if prev == cand:
|
|
450
|
+
delta_si = _get_attr(nd, ALIAS_dSI, 0.0)
|
|
451
|
+
h = G.graph.get("history", {})
|
|
452
|
+
sig = h.get("sense_sigma_mag", [])
|
|
453
|
+
delta_sigma = sig[-1] - sig[-2] if len(sig) >= 2 else 0.0
|
|
454
|
+
if delta_si <= 0.0 and delta_sigma <= 0.0:
|
|
455
|
+
score -= 0.05
|
|
406
456
|
|
|
407
457
|
# Override suave guiado por score (solo si NO cayó la histéresis arriba)
|
|
408
458
|
# Regla: score>=0.66 inclina a I’L; score<=0.33 inclina a O’Z/Z’HIR
|
|
@@ -412,22 +462,9 @@ def parametric_glyph_selector(G, n) -> str:
|
|
|
412
462
|
elif score <= 0.33 and cand in ("NA’V","R’A","I’L"):
|
|
413
463
|
cand = "O’Z" if dnfr >= dnfr_lo else "Z’HIR"
|
|
414
464
|
except NameError:
|
|
415
|
-
# por si 'score' no se definió (robustez), no forzamos nada
|
|
416
465
|
pass
|
|
417
466
|
|
|
418
|
-
|
|
419
|
-
gram = G.graph.get("GRAMMAR", DEFAULTS.get("GRAMMAR", {}))
|
|
420
|
-
gwin = int(gram.get("window", 3))
|
|
421
|
-
avoid = set(gram.get("avoid_repeats", []))
|
|
422
|
-
force_dn = float(gram.get("force_dnfr", 0.60))
|
|
423
|
-
force_ac = float(gram.get("force_accel", 0.60))
|
|
424
|
-
fallbacks = gram.get("fallbacks", {})
|
|
425
|
-
|
|
426
|
-
if cand in avoid and reciente_glifo(nd, cand, gwin):
|
|
427
|
-
# Solo permitimos repetir si el campo "insiste": dnfr o accel altos (ya normalizados)
|
|
428
|
-
if not (dnfr >= force_dn or accel >= force_ac):
|
|
429
|
-
cand = fallbacks.get(cand, "R’A")
|
|
430
|
-
|
|
467
|
+
cand = _soft_grammar_prefilter(G, n, cand, dnfr, accel)
|
|
431
468
|
return cand
|
|
432
469
|
|
|
433
470
|
# -------------------------
|
|
@@ -470,7 +507,7 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
|
|
|
470
507
|
|
|
471
508
|
# 5) Clamps
|
|
472
509
|
for n in G.nodes():
|
|
473
|
-
aplicar_clamps_canonicos(G.nodes[n], G)
|
|
510
|
+
aplicar_clamps_canonicos(G.nodes[n], G, n)
|
|
474
511
|
|
|
475
512
|
# 6) Coordinación de fase
|
|
476
513
|
coordinar_fase_global_vecinal(G, None, None)
|
|
@@ -478,7 +515,9 @@ def step(G, *, dt: float | None = None, use_Si: bool = True, apply_glyphs: bool
|
|
|
478
515
|
# 7) Observadores ligeros
|
|
479
516
|
_update_history(G)
|
|
480
517
|
# dynamics.py — dentro de step(), justo antes del punto 8)
|
|
481
|
-
|
|
518
|
+
tau_g = int(G.graph.get("REMESH_TAU_GLOBAL", G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU_GLOBAL"])))
|
|
519
|
+
tau_l = int(G.graph.get("REMESH_TAU_LOCAL", G.graph.get("REMESH_TAU", DEFAULTS["REMESH_TAU_LOCAL"])))
|
|
520
|
+
tau = max(tau_g, tau_l)
|
|
482
521
|
maxlen = max(2 * tau + 5, 64)
|
|
483
522
|
epi_hist = G.graph.get("_epi_hist")
|
|
484
523
|
if not isinstance(epi_hist, deque) or epi_hist.maxlen != maxlen:
|
|
@@ -519,11 +558,12 @@ def run(G, steps: int, *, dt: float | None = None, use_Si: bool = True, apply_gl
|
|
|
519
558
|
# -------------------------
|
|
520
559
|
|
|
521
560
|
def _update_history(G) -> None:
|
|
522
|
-
hist = G.graph.setdefault("history", {
|
|
523
|
-
|
|
524
|
-
"
|
|
525
|
-
"Si_mean"
|
|
526
|
-
|
|
561
|
+
hist = G.graph.setdefault("history", {})
|
|
562
|
+
for k in (
|
|
563
|
+
"C_steps", "stable_frac", "phase_sync", "glyph_load_estab", "glyph_load_disr",
|
|
564
|
+
"Si_mean", "Si_hi_frac", "Si_lo_frac", "delta_Si", "B"
|
|
565
|
+
):
|
|
566
|
+
hist.setdefault(k, [])
|
|
527
567
|
|
|
528
568
|
# Proxy de coherencia C(t)
|
|
529
569
|
dnfr_mean = list_mean(abs(_get_attr(G.nodes[n], ALIAS_DNFR, 0.0)) for n in G.nodes())
|
|
@@ -543,11 +583,37 @@ def _update_history(G) -> None:
|
|
|
543
583
|
eps_depi = float(G.graph.get("EPS_DEPI_STABLE", DEFAULTS["EPS_DEPI_STABLE"]))
|
|
544
584
|
stables = 0
|
|
545
585
|
total = max(1, G.number_of_nodes())
|
|
586
|
+
dt = float(G.graph.get("DT", DEFAULTS.get("DT", 1.0))) or 1.0
|
|
587
|
+
delta_si_acc = []
|
|
588
|
+
B_acc = []
|
|
546
589
|
for n in G.nodes():
|
|
547
590
|
nd = G.nodes[n]
|
|
548
591
|
if abs(_get_attr(nd, ALIAS_DNFR, 0.0)) <= eps_dnfr and abs(_get_attr(nd, ALIAS_dEPI, 0.0)) <= eps_depi:
|
|
549
592
|
stables += 1
|
|
593
|
+
|
|
594
|
+
# δSi por nodo
|
|
595
|
+
Si_curr = _get_attr(nd, ALIAS_SI, 0.0)
|
|
596
|
+
Si_prev = nd.get("_prev_Si", Si_curr)
|
|
597
|
+
dSi = Si_curr - Si_prev
|
|
598
|
+
nd["_prev_Si"] = Si_curr
|
|
599
|
+
_set_attr(nd, ALIAS_dSI, dSi)
|
|
600
|
+
delta_si_acc.append(dSi)
|
|
601
|
+
|
|
602
|
+
# Bifurcación B = ∂²νf/∂t²
|
|
603
|
+
vf_curr = _get_attr(nd, ALIAS_VF, 0.0)
|
|
604
|
+
vf_prev = nd.get("_prev_vf", vf_curr)
|
|
605
|
+
dvf_dt = (vf_curr - vf_prev) / dt
|
|
606
|
+
dvf_prev = nd.get("_prev_dvf", dvf_dt)
|
|
607
|
+
B = (dvf_dt - dvf_prev) / dt
|
|
608
|
+
nd["_prev_vf"] = vf_curr
|
|
609
|
+
nd["_prev_dvf"] = dvf_dt
|
|
610
|
+
_set_attr(nd, ALIAS_dVF, dvf_dt)
|
|
611
|
+
_set_attr(nd, ALIAS_D2VF, B)
|
|
612
|
+
B_acc.append(B)
|
|
613
|
+
|
|
550
614
|
hist["stable_frac"].append(stables/total)
|
|
615
|
+
hist["delta_Si"].append(list_mean(delta_si_acc, 0.0))
|
|
616
|
+
hist["B"].append(list_mean(B_acc, 0.0))
|
|
551
617
|
# --- nuevas series: sincronía de fase y carga glífica ---
|
|
552
618
|
try:
|
|
553
619
|
ps = sincronía_fase(G) # [0,1], más alto = más en fase
|