tnfr 4.1.0__tar.gz → 4.5.0__tar.gz
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-4.1.0/src/tnfr.egg-info → tnfr-4.5.0}/PKG-INFO +10 -4
- {tnfr-4.1.0 → tnfr-4.5.0}/README.md +7 -1
- {tnfr-4.1.0 → tnfr-4.5.0}/pyproject.toml +5 -4
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/__init__.py +34 -4
- tnfr-4.5.0/src/tnfr/cli.py +322 -0
- tnfr-4.5.0/src/tnfr/config.py +41 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/constants.py +102 -41
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/dynamics.py +255 -49
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/gamma.py +35 -8
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/helpers.py +50 -17
- tnfr-4.5.0/src/tnfr/metrics.py +597 -0
- tnfr-4.5.0/src/tnfr/node.py +202 -0
- tnfr-4.5.0/src/tnfr/operators.py +525 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/presets.py +3 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/scenarios.py +9 -3
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/sense.py +6 -21
- tnfr-4.5.0/src/tnfr/structural.py +201 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/trace.py +4 -20
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/types.py +2 -1
- tnfr-4.5.0/src/tnfr/validators.py +38 -0
- {tnfr-4.1.0 → tnfr-4.5.0/src/tnfr.egg-info}/PKG-INFO +10 -4
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr.egg-info/SOURCES.txt +13 -1
- tnfr-4.5.0/tests/test_canon.py +30 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_edge_cases.py +1 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_grammar.py +13 -0
- tnfr-4.5.0/tests/test_history_series.py +27 -0
- tnfr-4.5.0/tests/test_integrators.py +36 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_invariants.py +5 -0
- tnfr-4.5.0/tests/test_nav.py +32 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_remesh.py +21 -0
- tnfr-4.5.0/tests/test_remesh_community.py +18 -0
- tnfr-4.5.0/tests/test_structural.py +47 -0
- tnfr-4.5.0/tests/test_validators.py +38 -0
- tnfr-4.5.0/tests/test_vf_coherencia.py +27 -0
- tnfr-4.1.0/src/tnfr/cli.py +0 -193
- tnfr-4.1.0/src/tnfr/metrics.py +0 -211
- tnfr-4.1.0/src/tnfr/operators.py +0 -330
- {tnfr-4.1.0 → tnfr-4.5.0}/LICENSE.md +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/setup.cfg +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/grammar.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/main.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/observers.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/ontosim.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr/program.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr.egg-info/dependency_links.txt +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr.egg-info/entry_points.txt +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr.egg-info/requires.txt +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/src/tnfr.egg-info/top_level.txt +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_cli_sanity.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_gamma.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_history.py +0 -0
- {tnfr-4.1.0 → tnfr-4.5.0}/tests/test_program.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tnfr
|
|
3
|
-
Version: 4.
|
|
4
|
-
Summary: TNFR
|
|
3
|
+
Version: 4.5.0
|
|
4
|
+
Summary: Canonical TNFR: modular glyph-based dynamics on networks.
|
|
5
5
|
Author: fmg
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://pypi.org/project/tnfr/
|
|
8
8
|
Project-URL: Repository, https://github.com/fermga/Teoria-de-la-naturaleza-fractal-resonante-TNFR-
|
|
9
|
-
Keywords: TNFR,fractal
|
|
9
|
+
Keywords: TNFR,resonant fractal,resonance,glyphs,networkx,dynamics,coherence,EPI,Kuramoto
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -25,7 +25,13 @@ License-File: LICENSE.md
|
|
|
25
25
|
Requires-Dist: networkx>=2.6
|
|
26
26
|
Dynamic: license-file
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
# TNFR Python Project
|
|
29
|
+
|
|
30
|
+
Reference implementation of the Resonant Fractal Nature Theory (TNFR).
|
|
31
|
+
It models glyph-driven dynamics on NetworkX graphs, providing a modular
|
|
32
|
+
engine to simulate coherent reorganization processes.
|
|
33
|
+
|
|
34
|
+
## General Project Structure
|
|
29
35
|
|
|
30
36
|
* **Package entry point.** `__init__.py` registers modules under short names to avoid circular imports and exposes the public API: `preparar_red`, `step`, `run`, and observation utilities.
|
|
31
37
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TNFR Python Project
|
|
2
|
+
|
|
3
|
+
Reference implementation of the Resonant Fractal Nature Theory (TNFR).
|
|
4
|
+
It models glyph-driven dynamics on NetworkX graphs, providing a modular
|
|
5
|
+
engine to simulate coherent reorganization processes.
|
|
6
|
+
|
|
7
|
+
## General Project Structure
|
|
2
8
|
|
|
3
9
|
* **Package entry point.** `__init__.py` registers modules under short names to avoid circular imports and exposes the public API: `preparar_red`, `step`, `run`, and observation utilities.
|
|
4
10
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tnfr"
|
|
3
|
-
version = "4.
|
|
4
|
-
description = "TNFR
|
|
3
|
+
version = "4.5.0"
|
|
4
|
+
description = "Canonical TNFR: modular glyph-based dynamics on networks."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.9"
|
|
7
7
|
license = { text = "MIT" }
|
|
8
8
|
authors = [{ name = "fmg" }]
|
|
9
9
|
keywords = [
|
|
10
|
-
"TNFR", "fractal
|
|
11
|
-
"networkx", "
|
|
10
|
+
"TNFR", "resonant fractal", "resonance", "glyphs",
|
|
11
|
+
"networkx", "dynamics", "coherence", "EPI", "Kuramoto"
|
|
12
12
|
]
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
@@ -39,3 +39,4 @@ requires = ["setuptools>=61", "wheel"]
|
|
|
39
39
|
build-backend = "setuptools.build_meta"
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
|
|
@@ -8,10 +8,10 @@ Ecuación nodal:
|
|
|
8
8
|
∂EPI/∂t = νf · ΔNFR(t)
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
__version__ = "4.
|
|
11
|
+
__version__ = "4.5.0"
|
|
12
12
|
|
|
13
13
|
# Re-exports de la API pública
|
|
14
|
-
from .dynamics import step, run, set_delta_nfr_hook
|
|
14
|
+
from .dynamics import step, run, set_delta_nfr_hook, validate_canon
|
|
15
15
|
from .ontosim import preparar_red
|
|
16
16
|
from .observers import attach_standard_observer, coherencia_global, orden_kuramoto
|
|
17
17
|
from .gamma import GAMMA_REGISTRY, eval_gamma, kuramoto_R_psi
|
|
@@ -26,19 +26,40 @@ 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 .operators import aplicar_remesh_red_topologico
|
|
31
32
|
from .trace import register_trace
|
|
32
33
|
from .program import play, seq, block, target, wait, THOL, TARGET, WAIT, ejemplo_canonico_basico
|
|
33
34
|
from .cli import main as cli_main
|
|
34
35
|
from .scenarios import build_graph
|
|
35
36
|
from .presets import get_preset
|
|
36
37
|
from .types import NodeState
|
|
38
|
+
from .structural import (
|
|
39
|
+
create_nfr,
|
|
40
|
+
Operador,
|
|
41
|
+
Emision,
|
|
42
|
+
Recepcion,
|
|
43
|
+
Coherencia,
|
|
44
|
+
Disonancia,
|
|
45
|
+
Acoplamiento,
|
|
46
|
+
Resonancia,
|
|
47
|
+
Silencio,
|
|
48
|
+
Expansion,
|
|
49
|
+
Contraccion,
|
|
50
|
+
Autoorganizacion,
|
|
51
|
+
Mutacion,
|
|
52
|
+
Transicion,
|
|
53
|
+
Recursividad,
|
|
54
|
+
OPERADORES,
|
|
55
|
+
validate_sequence,
|
|
56
|
+
run_sequence,
|
|
57
|
+
)
|
|
37
58
|
|
|
38
59
|
|
|
39
60
|
__all__ = [
|
|
40
61
|
"preparar_red",
|
|
41
|
-
"step", "run", "set_delta_nfr_hook",
|
|
62
|
+
"step", "run", "set_delta_nfr_hook", "validate_canon",
|
|
42
63
|
|
|
43
64
|
"attach_standard_observer", "coherencia_global", "orden_kuramoto",
|
|
44
65
|
"GAMMA_REGISTRY", "eval_gamma", "kuramoto_R_psi",
|
|
@@ -52,8 +73,17 @@ __all__ = [
|
|
|
52
73
|
"Tg_global", "Tg_by_node",
|
|
53
74
|
"latency_series", "glifogram_series",
|
|
54
75
|
"glyph_top", "glyph_dwell_stats",
|
|
76
|
+
"export_history",
|
|
77
|
+
"aplicar_remesh_red_topologico",
|
|
55
78
|
"play", "seq", "block", "target", "wait", "THOL", "TARGET", "WAIT",
|
|
56
79
|
"cli_main", "build_graph", "get_preset", "NodeState",
|
|
57
80
|
"ejemplo_canonico_basico",
|
|
81
|
+
"create_nfr",
|
|
82
|
+
"Operador", "Emision", "Recepcion", "Coherencia", "Disonancia",
|
|
83
|
+
"Acoplamiento", "Resonancia", "Silencio", "Expansion", "Contraccion",
|
|
84
|
+
"Autoorganizacion", "Mutacion", "Transicion", "Recursividad",
|
|
85
|
+
"OPERADORES", "validate_sequence", "run_sequence",
|
|
58
86
|
"__version__",
|
|
59
87
|
]
|
|
88
|
+
|
|
89
|
+
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
try: # pragma: no cover - opcional
|
|
7
|
+
import yaml # type: ignore
|
|
8
|
+
except Exception: # pragma: no cover - yaml es opcional
|
|
9
|
+
yaml = None
|
|
10
|
+
|
|
11
|
+
import networkx as nx
|
|
12
|
+
|
|
13
|
+
from .constants import inject_defaults, DEFAULTS
|
|
14
|
+
from .sense import register_sigma_callback, sigma_series, sigma_rose
|
|
15
|
+
from .metrics import (
|
|
16
|
+
register_metrics_callbacks,
|
|
17
|
+
Tg_global,
|
|
18
|
+
latency_series,
|
|
19
|
+
glifogram_series,
|
|
20
|
+
glyph_top,
|
|
21
|
+
export_history,
|
|
22
|
+
)
|
|
23
|
+
from .trace import register_trace
|
|
24
|
+
from .program import play, seq, block, wait, target
|
|
25
|
+
from .dynamics import step, _update_history, default_glyph_selector, parametric_glyph_selector, validate_canon
|
|
26
|
+
from .gamma import GAMMA_REGISTRY
|
|
27
|
+
from .scenarios import build_graph
|
|
28
|
+
from .presets import get_preset
|
|
29
|
+
from .config import apply_config
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _save_json(path: str, data: Any) -> None:
|
|
33
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
34
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _str2bool(s: str) -> bool:
|
|
38
|
+
s = s.lower()
|
|
39
|
+
if s in {"true", "1", "yes", "y"}:
|
|
40
|
+
return True
|
|
41
|
+
if s in {"false", "0", "no", "n"}:
|
|
42
|
+
return False
|
|
43
|
+
raise argparse.ArgumentTypeError("expected true/false")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _args_to_dict(args: argparse.Namespace, prefix: str) -> Dict[str, Any]:
|
|
47
|
+
out: Dict[str, Any] = {}
|
|
48
|
+
pref = prefix.replace(".", "_")
|
|
49
|
+
for k, v in vars(args).items():
|
|
50
|
+
if k.startswith(pref) and v is not None:
|
|
51
|
+
out[k[len(pref):]] = v
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _load_sequence(path: str) -> List[Any]:
|
|
56
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
57
|
+
text = f.read()
|
|
58
|
+
if path.endswith(".yaml") or path.endswith(".yml"):
|
|
59
|
+
if not yaml:
|
|
60
|
+
raise RuntimeError("pyyaml no está instalado, usa JSON o instala pyyaml")
|
|
61
|
+
data = yaml.safe_load(text)
|
|
62
|
+
else:
|
|
63
|
+
data = json.loads(text)
|
|
64
|
+
|
|
65
|
+
def parse_token(tok: Any):
|
|
66
|
+
if isinstance(tok, str):
|
|
67
|
+
return tok
|
|
68
|
+
if isinstance(tok, dict):
|
|
69
|
+
if "WAIT" in tok:
|
|
70
|
+
return wait(int(tok["WAIT"]))
|
|
71
|
+
if "TARGET" in tok:
|
|
72
|
+
return target(tok["TARGET"])
|
|
73
|
+
if "THOL" in tok:
|
|
74
|
+
spec = tok["THOL"] or {}
|
|
75
|
+
b = [_parse_inner(x) for x in spec.get("body", [])]
|
|
76
|
+
return block(*b, repeat=int(spec.get("repeat", 1)), close=spec.get("close"))
|
|
77
|
+
raise ValueError(f"Token inválido: {tok}")
|
|
78
|
+
|
|
79
|
+
def _parse_inner(x: Any):
|
|
80
|
+
return parse_token(x)
|
|
81
|
+
|
|
82
|
+
return [parse_token(t) for t in data]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _attach_callbacks(G: nx.Graph) -> None:
|
|
86
|
+
inject_defaults(G, DEFAULTS)
|
|
87
|
+
register_sigma_callback(G)
|
|
88
|
+
register_metrics_callbacks(G)
|
|
89
|
+
register_trace(G)
|
|
90
|
+
_update_history(G)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
94
|
+
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
95
|
+
if getattr(args, "config", None):
|
|
96
|
+
apply_config(G, args.config)
|
|
97
|
+
_attach_callbacks(G)
|
|
98
|
+
validate_canon(G)
|
|
99
|
+
if args.dt is not None:
|
|
100
|
+
G.graph["DT"] = float(args.dt)
|
|
101
|
+
if args.integrator is not None:
|
|
102
|
+
G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
|
|
103
|
+
if getattr(args, "remesh_mode", None):
|
|
104
|
+
G.graph["REMESH_MODE"] = str(args.remesh_mode)
|
|
105
|
+
gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
|
|
106
|
+
gcanon.update(_args_to_dict(args, prefix="grammar."))
|
|
107
|
+
if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
|
|
108
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
109
|
+
G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
|
|
110
|
+
if args.glyph_hysteresis_window is not None:
|
|
111
|
+
G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
|
|
112
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
113
|
+
G.graph["GAMMA"] = {
|
|
114
|
+
"type": args.gamma_type,
|
|
115
|
+
"beta": args.gamma_beta,
|
|
116
|
+
"R0": args.gamma_R0,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if args.preset:
|
|
120
|
+
program = get_preset(args.preset)
|
|
121
|
+
play(G, program)
|
|
122
|
+
else:
|
|
123
|
+
steps = int(args.steps or 100)
|
|
124
|
+
for _ in range(steps):
|
|
125
|
+
step(G)
|
|
126
|
+
|
|
127
|
+
if args.save_history:
|
|
128
|
+
_save_json(args.save_history, G.graph.get("history", {}))
|
|
129
|
+
if args.export_history_base:
|
|
130
|
+
export_history(G, args.export_history_base, fmt=args.export_format)
|
|
131
|
+
|
|
132
|
+
# Resúmenes rápidos (si están activados)
|
|
133
|
+
if G.graph.get("COHERENCE", DEFAULTS["COHERENCE"]).get("enabled", True):
|
|
134
|
+
Wstats = G.graph.get("history", {}).get(
|
|
135
|
+
G.graph.get("COHERENCE", DEFAULTS["COHERENCE"]).get("stats_history_key", "W_stats"), []
|
|
136
|
+
)
|
|
137
|
+
if Wstats:
|
|
138
|
+
print("[COHERENCE] último paso:", Wstats[-1])
|
|
139
|
+
if G.graph.get("DIAGNOSIS", DEFAULTS["DIAGNOSIS"]).get("enabled", True):
|
|
140
|
+
last_diag = G.graph.get("history", {}).get(
|
|
141
|
+
G.graph.get("DIAGNOSIS", DEFAULTS["DIAGNOSIS"]).get("history_key", "nodal_diag"), []
|
|
142
|
+
)
|
|
143
|
+
if last_diag:
|
|
144
|
+
sample = list(last_diag[-1].values())[:3]
|
|
145
|
+
print("[DIAGNOSIS] ejemplo:", sample)
|
|
146
|
+
|
|
147
|
+
if args.summary:
|
|
148
|
+
tg = Tg_global(G, normalize=True)
|
|
149
|
+
lat = latency_series(G)
|
|
150
|
+
print("Top operadores por Tg:", glyph_top(G, k=5))
|
|
151
|
+
if lat["value"]:
|
|
152
|
+
print("Latencia media:", sum(lat["value"]) / max(1, len(lat["value"])) )
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def cmd_sequence(args: argparse.Namespace) -> int:
|
|
157
|
+
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
158
|
+
if getattr(args, "config", None):
|
|
159
|
+
apply_config(G, args.config)
|
|
160
|
+
_attach_callbacks(G)
|
|
161
|
+
validate_canon(G)
|
|
162
|
+
if args.dt is not None:
|
|
163
|
+
G.graph["DT"] = float(args.dt)
|
|
164
|
+
if args.integrator is not None:
|
|
165
|
+
G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
|
|
166
|
+
if getattr(args, "remesh_mode", None):
|
|
167
|
+
G.graph["REMESH_MODE"] = str(args.remesh_mode)
|
|
168
|
+
gcanon = dict(DEFAULTS["GRAMMAR_CANON"])
|
|
169
|
+
gcanon.update(_args_to_dict(args, prefix="grammar."))
|
|
170
|
+
if hasattr(args, "grammar_canon") and args.grammar_canon is not None:
|
|
171
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
172
|
+
G.graph.setdefault("GRAMMAR_CANON", {}).update(gcanon)
|
|
173
|
+
if args.glyph_hysteresis_window is not None:
|
|
174
|
+
G.graph["GLYPH_HYSTERESIS_WINDOW"] = int(args.glyph_hysteresis_window)
|
|
175
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
176
|
+
G.graph["GAMMA"] = {
|
|
177
|
+
"type": args.gamma_type,
|
|
178
|
+
"beta": args.gamma_beta,
|
|
179
|
+
"R0": args.gamma_R0,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if args.preset:
|
|
183
|
+
program = get_preset(args.preset)
|
|
184
|
+
elif args.sequence_file:
|
|
185
|
+
program = _load_sequence(args.sequence_file)
|
|
186
|
+
else:
|
|
187
|
+
program = seq("A’L", "E’N", "I’L", block("O’Z", "Z’HIR", "I’L", repeat=1), "R’A", "SH’A")
|
|
188
|
+
|
|
189
|
+
play(G, program)
|
|
190
|
+
|
|
191
|
+
if args.save_history:
|
|
192
|
+
_save_json(args.save_history, G.graph.get("history", {}))
|
|
193
|
+
if args.export_history_base:
|
|
194
|
+
export_history(G, args.export_history_base, fmt=args.export_format)
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def cmd_metrics(args: argparse.Namespace) -> int:
|
|
199
|
+
G = build_graph(n=args.nodes, topology=args.topology, seed=args.seed)
|
|
200
|
+
if getattr(args, "config", None):
|
|
201
|
+
apply_config(G, args.config)
|
|
202
|
+
_attach_callbacks(G)
|
|
203
|
+
validate_canon(G)
|
|
204
|
+
if args.dt is not None:
|
|
205
|
+
G.graph["DT"] = float(args.dt)
|
|
206
|
+
if args.integrator is not None:
|
|
207
|
+
G.graph["INTEGRATOR_METHOD"] = str(args.integrator)
|
|
208
|
+
if getattr(args, "remesh_mode", None):
|
|
209
|
+
G.graph["REMESH_MODE"] = str(args.remesh_mode)
|
|
210
|
+
G.graph.setdefault("GRAMMAR_CANON", DEFAULTS["GRAMMAR_CANON"]).update({"enabled": bool(args.grammar_canon)})
|
|
211
|
+
G.graph["glyph_selector"] = default_glyph_selector if args.selector == "basic" else parametric_glyph_selector
|
|
212
|
+
G.graph["GAMMA"] = {
|
|
213
|
+
"type": args.gamma_type,
|
|
214
|
+
"beta": args.gamma_beta,
|
|
215
|
+
"R0": args.gamma_R0,
|
|
216
|
+
}
|
|
217
|
+
for _ in range(int(args.steps or 200)):
|
|
218
|
+
step(G)
|
|
219
|
+
|
|
220
|
+
tg = Tg_global(G, normalize=True)
|
|
221
|
+
lat = latency_series(G)
|
|
222
|
+
rose = sigma_rose(G)
|
|
223
|
+
glifo = glifogram_series(G)
|
|
224
|
+
|
|
225
|
+
out = {
|
|
226
|
+
"Tg_global": tg,
|
|
227
|
+
"latency_mean": (sum(lat["value"]) / max(1, len(lat["value"])) ) if lat["value"] else 0.0,
|
|
228
|
+
"rose": rose,
|
|
229
|
+
"glifogram": {k: v[:10] for k, v in glifo.items()},
|
|
230
|
+
}
|
|
231
|
+
if args.save:
|
|
232
|
+
_save_json(args.save, out)
|
|
233
|
+
else:
|
|
234
|
+
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
239
|
+
p = argparse.ArgumentParser(prog="tnfr")
|
|
240
|
+
sub = p.add_subparsers(dest="cmd")
|
|
241
|
+
|
|
242
|
+
p_run = sub.add_parser("run", help="Correr escenario libre o preset y opcionalmente exportar history")
|
|
243
|
+
p_run.add_argument("--nodes", type=int, default=24)
|
|
244
|
+
p_run.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
|
|
245
|
+
p_run.add_argument("--steps", type=int, default=200)
|
|
246
|
+
p_run.add_argument("--seed", type=int, default=1)
|
|
247
|
+
p_run.add_argument("--preset", type=str, default=None)
|
|
248
|
+
p_run.add_argument("--config", type=str, default=None)
|
|
249
|
+
p_run.add_argument("--dt", type=float, default=None)
|
|
250
|
+
p_run.add_argument("--integrator", choices=["euler", "rk4"], default=None)
|
|
251
|
+
p_run.add_argument("--save-history", dest="save_history", type=str, default=None)
|
|
252
|
+
p_run.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
|
|
253
|
+
p_run.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
|
|
254
|
+
p_run.add_argument("--summary", action="store_true")
|
|
255
|
+
p_run.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
|
|
256
|
+
p_run.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
|
|
257
|
+
p_run.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
|
|
258
|
+
p_run.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
|
|
259
|
+
p_run.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
|
|
260
|
+
p_run.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
|
|
261
|
+
p_run.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
|
|
262
|
+
p_run.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
|
|
263
|
+
p_run.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
|
|
264
|
+
p_run.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
|
|
265
|
+
p_run.add_argument("--selector", choices=["basic", "param"], default="basic")
|
|
266
|
+
p_run.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
267
|
+
p_run.add_argument("--gamma-beta", type=float, default=0.0)
|
|
268
|
+
p_run.add_argument("--gamma-R0", type=float, default=0.0)
|
|
269
|
+
p_run.set_defaults(func=cmd_run)
|
|
270
|
+
|
|
271
|
+
p_seq = sub.add_parser("sequence", help="Ejecutar una secuencia (preset o YAML/JSON)")
|
|
272
|
+
p_seq.add_argument("--nodes", type=int, default=24)
|
|
273
|
+
p_seq.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
|
|
274
|
+
p_seq.add_argument("--seed", type=int, default=1)
|
|
275
|
+
p_seq.add_argument("--preset", type=str, default=None)
|
|
276
|
+
p_seq.add_argument("--sequence-file", type=str, default=None)
|
|
277
|
+
p_seq.add_argument("--config", type=str, default=None)
|
|
278
|
+
p_seq.add_argument("--dt", type=float, default=None)
|
|
279
|
+
p_seq.add_argument("--integrator", choices=["euler", "rk4"], default=None)
|
|
280
|
+
p_seq.add_argument("--save-history", dest="save_history", type=str, default=None)
|
|
281
|
+
p_seq.add_argument("--export-history-base", dest="export_history_base", type=str, default=None)
|
|
282
|
+
p_seq.add_argument("--export-format", dest="export_format", choices=["csv", "json"], default="json")
|
|
283
|
+
p_seq.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
|
|
284
|
+
p_seq.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
285
|
+
p_seq.add_argument("--gamma-beta", type=float, default=0.0)
|
|
286
|
+
p_seq.add_argument("--gamma-R0", type=float, default=0.0)
|
|
287
|
+
p_seq.add_argument("--grammar.enabled", dest="grammar_enabled", type=_str2bool, default=None)
|
|
288
|
+
p_seq.add_argument("--grammar.zhir_requires_oz_window", dest="grammar_zhir_requires_oz_window", type=int, default=None)
|
|
289
|
+
p_seq.add_argument("--grammar.zhir_dnfr_min", dest="grammar_zhir_dnfr_min", type=float, default=None)
|
|
290
|
+
p_seq.add_argument("--grammar.thol_min_len", dest="grammar_thol_min_len", type=int, default=None)
|
|
291
|
+
p_seq.add_argument("--grammar.thol_max_len", dest="grammar_thol_max_len", type=int, default=None)
|
|
292
|
+
p_seq.add_argument("--grammar.thol_close_dnfr", dest="grammar_thol_close_dnfr", type=float, default=None)
|
|
293
|
+
p_seq.add_argument("--grammar.si_high", dest="grammar_si_high", type=float, default=None)
|
|
294
|
+
p_seq.add_argument("--glyph.hysteresis_window", dest="glyph_hysteresis_window", type=int, default=None)
|
|
295
|
+
p_seq.set_defaults(func=cmd_sequence)
|
|
296
|
+
|
|
297
|
+
p_met = sub.add_parser("metrics", help="Correr breve y volcar métricas clave")
|
|
298
|
+
p_met.add_argument("--nodes", type=int, default=24)
|
|
299
|
+
p_met.add_argument("--topology", choices=["ring", "complete", "erdos"], default="ring")
|
|
300
|
+
p_met.add_argument("--steps", type=int, default=300)
|
|
301
|
+
p_met.add_argument("--seed", type=int, default=1)
|
|
302
|
+
p_met.add_argument("--dt", type=float, default=None)
|
|
303
|
+
p_met.add_argument("--integrator", choices=["euler", "rk4"], default=None)
|
|
304
|
+
p_met.add_argument("--no-canon", dest="grammar_canon", action="store_false", default=True, help="Desactiva gramática canónica")
|
|
305
|
+
p_met.add_argument("--selector", choices=["basic", "param"], default="basic")
|
|
306
|
+
p_met.add_argument("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none")
|
|
307
|
+
p_met.add_argument("--gamma-beta", type=float, default=0.0)
|
|
308
|
+
p_met.add_argument("--gamma-R0", type=float, default=0.0)
|
|
309
|
+
p_met.add_argument("--remesh-mode", choices=["knn", "mst", "community"], default=None)
|
|
310
|
+
p_met.add_argument("--save", type=str, default=None)
|
|
311
|
+
p_met.add_argument("--config", type=str, default=None)
|
|
312
|
+
p_met.set_defaults(func=cmd_metrics)
|
|
313
|
+
|
|
314
|
+
args = p.parse_args(argv)
|
|
315
|
+
if not hasattr(args, "func"):
|
|
316
|
+
p.print_help()
|
|
317
|
+
return 1
|
|
318
|
+
return int(args.func(args))
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__": # pragma: no cover
|
|
322
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Carga e inyección de configuraciones externas.
|
|
2
|
+
|
|
3
|
+
Permite definir parámetros en JSON o YAML y aplicarlos sobre ``G.graph``
|
|
4
|
+
reutilizando :func:`tnfr.constants.inject_defaults`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
try: # pragma: no cover - dependencia opcional
|
|
12
|
+
import yaml # type: ignore
|
|
13
|
+
except Exception: # pragma: no cover
|
|
14
|
+
yaml = None
|
|
15
|
+
|
|
16
|
+
from .constants import inject_defaults
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_config(path: str) -> Dict[str, Any]:
|
|
20
|
+
"""Lee un archivo JSON/YAML y devuelve un ``dict`` con los parámetros."""
|
|
21
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
22
|
+
text = f.read()
|
|
23
|
+
if path.endswith((".yaml", ".yml")):
|
|
24
|
+
if not yaml: # pragma: no cover - fallo en entorno sin pyyaml
|
|
25
|
+
raise RuntimeError("pyyaml no está instalado")
|
|
26
|
+
data = yaml.safe_load(text)
|
|
27
|
+
else:
|
|
28
|
+
data = json.loads(text)
|
|
29
|
+
if not isinstance(data, dict):
|
|
30
|
+
raise ValueError("El archivo de configuración debe contener un objeto")
|
|
31
|
+
return data
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def apply_config(G, path: str) -> None:
|
|
35
|
+
"""Inyecta parámetros desde ``path`` sobre ``G.graph``.
|
|
36
|
+
|
|
37
|
+
Se reutiliza :func:`inject_defaults` para mantener la semántica de los
|
|
38
|
+
*defaults* canónicos.
|
|
39
|
+
"""
|
|
40
|
+
cfg = load_config(path)
|
|
41
|
+
inject_defaults(G, cfg, override=True)
|