tnfr 4.5.1__py3-none-any.whl → 6.0.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.
- tnfr/__init__.py +270 -90
- tnfr/__init__.pyi +40 -0
- tnfr/_compat.py +11 -0
- tnfr/_version.py +7 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +631 -0
- tnfr/alias.pyi +140 -0
- tnfr/cache.py +732 -0
- tnfr/cache.pyi +232 -0
- tnfr/callback_utils.py +381 -0
- tnfr/callback_utils.pyi +105 -0
- tnfr/cli/__init__.py +89 -0
- tnfr/cli/__init__.pyi +47 -0
- tnfr/cli/arguments.py +199 -0
- tnfr/cli/arguments.pyi +33 -0
- tnfr/cli/execution.py +322 -0
- tnfr/cli/execution.pyi +80 -0
- tnfr/cli/utils.py +34 -0
- tnfr/cli/utils.pyi +8 -0
- tnfr/config/__init__.py +12 -0
- tnfr/config/__init__.pyi +8 -0
- tnfr/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/init.py +36 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +106 -0
- tnfr/config/operator_names.pyi +28 -0
- tnfr/config/presets.py +104 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/constants/__init__.py +228 -0
- tnfr/constants/__init__.pyi +104 -0
- tnfr/constants/core.py +158 -0
- tnfr/constants/core.pyi +17 -0
- tnfr/constants/init.py +31 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +102 -0
- tnfr/constants/metric.pyi +19 -0
- tnfr/constants_glyphs.py +16 -0
- tnfr/constants_glyphs.pyi +12 -0
- tnfr/dynamics/__init__.py +136 -0
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +201 -0
- tnfr/dynamics/aliases.py +22 -0
- tnfr/dynamics/coordination.py +343 -0
- tnfr/dynamics/dnfr.py +2315 -0
- tnfr/dynamics/dnfr.pyi +33 -0
- tnfr/dynamics/integrators.py +561 -0
- tnfr/dynamics/integrators.pyi +35 -0
- tnfr/dynamics/runtime.py +521 -0
- tnfr/dynamics/sampling.py +34 -0
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +680 -0
- tnfr/execution.py +216 -0
- tnfr/execution.pyi +65 -0
- tnfr/flatten.py +283 -0
- tnfr/flatten.pyi +28 -0
- tnfr/gamma.py +320 -89
- tnfr/gamma.pyi +40 -0
- tnfr/glyph_history.py +337 -0
- tnfr/glyph_history.pyi +53 -0
- tnfr/grammar.py +23 -153
- tnfr/grammar.pyi +13 -0
- tnfr/helpers/__init__.py +151 -0
- tnfr/helpers/__init__.pyi +66 -0
- tnfr/helpers/numeric.py +88 -0
- tnfr/helpers/numeric.pyi +12 -0
- tnfr/immutable.py +214 -0
- tnfr/immutable.pyi +37 -0
- tnfr/initialization.py +199 -0
- tnfr/initialization.pyi +73 -0
- tnfr/io.py +311 -0
- tnfr/io.pyi +11 -0
- tnfr/locking.py +37 -0
- tnfr/locking.pyi +7 -0
- tnfr/metrics/__init__.py +41 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/coherence.py +1469 -0
- tnfr/metrics/common.py +149 -0
- tnfr/metrics/common.pyi +15 -0
- tnfr/metrics/core.py +259 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +840 -0
- tnfr/metrics/diagnosis.pyi +89 -0
- tnfr/metrics/export.py +151 -0
- tnfr/metrics/glyph_timing.py +369 -0
- tnfr/metrics/reporting.py +152 -0
- tnfr/metrics/reporting.pyi +12 -0
- tnfr/metrics/sense_index.py +294 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +216 -0
- tnfr/metrics/trig.pyi +12 -0
- tnfr/metrics/trig_cache.py +105 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/node.py +255 -177
- tnfr/node.pyi +161 -0
- tnfr/observers.py +154 -150
- tnfr/observers.pyi +46 -0
- tnfr/ontosim.py +135 -134
- tnfr/ontosim.pyi +33 -0
- tnfr/operators/__init__.py +452 -0
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/definitions.py +181 -0
- tnfr/operators/definitions.pyi +92 -0
- tnfr/operators/jitter.py +266 -0
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/registry.py +80 -0
- tnfr/operators/registry.pyi +15 -0
- tnfr/operators/remesh.py +569 -0
- tnfr/presets.py +10 -23
- tnfr/presets.pyi +7 -0
- tnfr/py.typed +0 -0
- tnfr/rng.py +440 -0
- tnfr/rng.pyi +14 -0
- tnfr/selector.py +217 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +307 -142
- tnfr/sense.pyi +30 -0
- tnfr/structural.py +69 -164
- tnfr/structural.pyi +46 -0
- tnfr/telemetry/__init__.py +13 -0
- tnfr/telemetry/verbosity.py +37 -0
- tnfr/tokens.py +61 -0
- tnfr/tokens.pyi +41 -0
- tnfr/trace.py +520 -95
- tnfr/trace.pyi +68 -0
- tnfr/types.py +382 -17
- tnfr/types.pyi +145 -0
- tnfr/utils/__init__.py +158 -0
- tnfr/utils/__init__.pyi +133 -0
- tnfr/utils/cache.py +755 -0
- tnfr/utils/cache.pyi +156 -0
- tnfr/utils/data.py +267 -0
- tnfr/utils/data.pyi +73 -0
- tnfr/utils/graph.py +87 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +746 -0
- tnfr/utils/init.pyi +85 -0
- tnfr/utils/io.py +157 -0
- tnfr/utils/io.pyi +10 -0
- tnfr/utils/validators.py +130 -0
- tnfr/utils/validators.pyi +19 -0
- tnfr/validation/__init__.py +25 -0
- tnfr/validation/__init__.pyi +17 -0
- tnfr/validation/compatibility.py +59 -0
- tnfr/validation/compatibility.pyi +8 -0
- tnfr/validation/grammar.py +149 -0
- tnfr/validation/grammar.pyi +11 -0
- tnfr/validation/rules.py +194 -0
- tnfr/validation/rules.pyi +18 -0
- tnfr/validation/syntax.py +151 -0
- tnfr/validation/syntax.pyi +7 -0
- tnfr-6.0.0.dist-info/METADATA +135 -0
- tnfr-6.0.0.dist-info/RECORD +157 -0
- tnfr/cli.py +0 -322
- tnfr/config.py +0 -41
- tnfr/constants.py +0 -277
- tnfr/dynamics.py +0 -814
- tnfr/helpers.py +0 -264
- tnfr/main.py +0 -47
- tnfr/metrics.py +0 -597
- tnfr/operators.py +0 -525
- tnfr/program.py +0 -176
- tnfr/scenarios.py +0 -34
- tnfr/validators.py +0 -38
- tnfr-4.5.1.dist-info/METADATA +0 -221
- tnfr-4.5.1.dist-info/RECORD +0 -28
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/WHEEL +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/entry_points.txt +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/licenses/LICENSE.md +0 -0
- {tnfr-4.5.1.dist-info → tnfr-6.0.0.dist-info}/top_level.txt +0 -0
tnfr/cli/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .arguments import (
|
|
9
|
+
add_common_args,
|
|
10
|
+
add_grammar_args,
|
|
11
|
+
add_grammar_selector_args,
|
|
12
|
+
add_history_export_args,
|
|
13
|
+
add_canon_toggle,
|
|
14
|
+
_add_run_parser,
|
|
15
|
+
_add_sequence_parser,
|
|
16
|
+
_add_metrics_parser,
|
|
17
|
+
)
|
|
18
|
+
from .execution import (
|
|
19
|
+
build_basic_graph,
|
|
20
|
+
apply_cli_config,
|
|
21
|
+
register_callbacks_and_observer,
|
|
22
|
+
run_program,
|
|
23
|
+
resolve_program,
|
|
24
|
+
)
|
|
25
|
+
from .. import __version__
|
|
26
|
+
from ..utils import _configure_root, get_logger
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
__all__ = (
|
|
31
|
+
"main",
|
|
32
|
+
"add_common_args",
|
|
33
|
+
"add_grammar_args",
|
|
34
|
+
"add_grammar_selector_args",
|
|
35
|
+
"add_history_export_args",
|
|
36
|
+
"add_canon_toggle",
|
|
37
|
+
"build_basic_graph",
|
|
38
|
+
"apply_cli_config",
|
|
39
|
+
"register_callbacks_and_observer",
|
|
40
|
+
"run_program",
|
|
41
|
+
"resolve_program",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
46
|
+
_configure_root()
|
|
47
|
+
|
|
48
|
+
root = logging.getLogger()
|
|
49
|
+
root.setLevel(logging.INFO)
|
|
50
|
+
|
|
51
|
+
formatter = logging.Formatter("%(message)s")
|
|
52
|
+
for handler in list(root.handlers):
|
|
53
|
+
root.removeHandler(handler)
|
|
54
|
+
|
|
55
|
+
handler = logging.StreamHandler(stream=sys.stdout)
|
|
56
|
+
handler.setLevel(logging.INFO)
|
|
57
|
+
handler.setFormatter(formatter)
|
|
58
|
+
root.addHandler(handler)
|
|
59
|
+
|
|
60
|
+
p = argparse.ArgumentParser(
|
|
61
|
+
prog="tnfr",
|
|
62
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
63
|
+
epilog=(
|
|
64
|
+
"Example: tnfr sequence --sequence-file sequence.json\n"
|
|
65
|
+
"sequence.json:\n"
|
|
66
|
+
'[\n {"WAIT": 1},\n {"TARGET": "A"}\n]'
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
p.add_argument(
|
|
70
|
+
"--version",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help=(
|
|
73
|
+
"show the actual version and exit (reads pyproject.toml in development)"
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
sub = p.add_subparsers(dest="cmd")
|
|
77
|
+
|
|
78
|
+
_add_run_parser(sub)
|
|
79
|
+
_add_sequence_parser(sub)
|
|
80
|
+
_add_metrics_parser(sub)
|
|
81
|
+
|
|
82
|
+
args = p.parse_args(argv)
|
|
83
|
+
if args.version:
|
|
84
|
+
logger.info("%s", __version__)
|
|
85
|
+
return 0
|
|
86
|
+
if not hasattr(args, "func"):
|
|
87
|
+
p.print_help()
|
|
88
|
+
return 1
|
|
89
|
+
return int(args.func(args))
|
tnfr/cli/__init__.pyi
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ..types import ProgramTokens, TNFRGraph
|
|
7
|
+
|
|
8
|
+
__all__: tuple[str, ...]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main(argv: Optional[list[str]] = None) -> int: ...
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None: ...
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def add_grammar_args(parser: argparse.ArgumentParser) -> None: ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def add_grammar_selector_args(parser: argparse.ArgumentParser) -> None: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def add_history_export_args(parser: argparse.ArgumentParser) -> None: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def add_canon_toggle(parser: argparse.ArgumentParser) -> None: ...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def build_basic_graph(args: argparse.Namespace) -> TNFRGraph: ...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def apply_cli_config(G: TNFRGraph, args: argparse.Namespace) -> None: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def register_callbacks_and_observer(G: TNFRGraph) -> None: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def resolve_program(
|
|
39
|
+
args: argparse.Namespace, default: Optional[ProgramTokens] = None
|
|
40
|
+
) -> Optional[ProgramTokens]: ...
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_program(
|
|
44
|
+
G: Optional[TNFRGraph],
|
|
45
|
+
program: Optional[ProgramTokens],
|
|
46
|
+
args: argparse.Namespace,
|
|
47
|
+
) -> TNFRGraph: ...
|
tnfr/cli/arguments.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Any, Iterable
|
|
5
|
+
|
|
6
|
+
from ..config.presets import PREFERRED_PRESET_NAMES
|
|
7
|
+
from ..gamma import GAMMA_REGISTRY
|
|
8
|
+
from ..telemetry.verbosity import TELEMETRY_VERBOSITY_LEVELS
|
|
9
|
+
from ..types import ArgSpec
|
|
10
|
+
from .utils import spec
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_PRESET_HELP = "Available presets: {}.".format(
|
|
14
|
+
", ".join(PREFERRED_PRESET_NAMES),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
TELEMETRY_VERBOSITY_CHOICES = TELEMETRY_VERBOSITY_LEVELS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
GRAMMAR_ARG_SPECS: tuple[ArgSpec, ...] = (
|
|
21
|
+
spec("--grammar.enabled", action=argparse.BooleanOptionalAction),
|
|
22
|
+
spec("--grammar.zhir_requires_oz_window", type=int),
|
|
23
|
+
spec("--grammar.zhir_dnfr_min", type=float),
|
|
24
|
+
spec("--grammar.thol_min_len", type=int),
|
|
25
|
+
spec("--grammar.thol_max_len", type=int),
|
|
26
|
+
spec("--grammar.thol_close_dnfr", type=float),
|
|
27
|
+
spec("--grammar.si_high", type=float),
|
|
28
|
+
spec("--glyph.hysteresis_window", type=int),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# History export/save specifications
|
|
33
|
+
HISTORY_ARG_SPECS: tuple[ArgSpec, ...] = (
|
|
34
|
+
spec("--save-history", type=str),
|
|
35
|
+
spec("--export-history-base", type=str),
|
|
36
|
+
spec("--export-format", choices=["csv", "json"], default="json"),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Arguments shared by CLI subcommands
|
|
41
|
+
COMMON_ARG_SPECS: tuple[ArgSpec, ...] = (
|
|
42
|
+
spec("--nodes", type=int, default=24),
|
|
43
|
+
spec("--topology", choices=["ring", "complete", "erdos"], default="ring"),
|
|
44
|
+
spec("--seed", type=int, default=1),
|
|
45
|
+
spec(
|
|
46
|
+
"--p",
|
|
47
|
+
type=float,
|
|
48
|
+
help="Edge probability when topology=erdos",
|
|
49
|
+
),
|
|
50
|
+
spec("--observer", action="store_true", help="Attach standard observer"),
|
|
51
|
+
spec(
|
|
52
|
+
"--trace-verbosity",
|
|
53
|
+
choices=TELEMETRY_VERBOSITY_CHOICES,
|
|
54
|
+
help="Select the trace capture preset",
|
|
55
|
+
),
|
|
56
|
+
spec(
|
|
57
|
+
"--metrics-verbosity",
|
|
58
|
+
choices=TELEMETRY_VERBOSITY_CHOICES,
|
|
59
|
+
help="Select the metrics capture preset",
|
|
60
|
+
),
|
|
61
|
+
spec("--config", type=str),
|
|
62
|
+
spec("--dt", type=float),
|
|
63
|
+
spec("--integrator", choices=["euler", "rk4"]),
|
|
64
|
+
spec("--remesh-mode", choices=["knn", "mst", "community"]),
|
|
65
|
+
spec("--gamma-type", choices=list(GAMMA_REGISTRY.keys()), default="none"),
|
|
66
|
+
spec("--gamma-beta", type=float, default=0.0),
|
|
67
|
+
spec("--gamma-R0", type=float, default=0.0),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def add_arg_specs(
|
|
72
|
+
parser: argparse._ActionsContainer, specs: Iterable[ArgSpec]
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Register arguments from ``specs`` on ``parser``."""
|
|
75
|
+
for opt, kwargs in specs:
|
|
76
|
+
parser.add_argument(opt, **kwargs)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _args_to_dict(args: argparse.Namespace, prefix: str) -> dict[str, Any]:
|
|
80
|
+
"""Extract arguments matching a prefix."""
|
|
81
|
+
return {
|
|
82
|
+
k.removeprefix(prefix): v
|
|
83
|
+
for k, v in vars(args).items()
|
|
84
|
+
if k.startswith(prefix) and v is not None
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None:
|
|
89
|
+
"""Add arguments shared across subcommands."""
|
|
90
|
+
add_arg_specs(parser, COMMON_ARG_SPECS)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def add_grammar_args(parser: argparse.ArgumentParser) -> None:
|
|
94
|
+
"""Add grammar and glyph hysteresis options."""
|
|
95
|
+
group = parser.add_argument_group("Grammar")
|
|
96
|
+
add_arg_specs(group, GRAMMAR_ARG_SPECS)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def add_grammar_selector_args(parser: argparse.ArgumentParser) -> None:
|
|
100
|
+
"""Add grammar options and glyph selector."""
|
|
101
|
+
add_grammar_args(parser)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--selector", choices=["basic", "param"], default="basic"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def add_history_export_args(parser: argparse.ArgumentParser) -> None:
|
|
108
|
+
"""Add arguments to save or export history."""
|
|
109
|
+
add_arg_specs(parser, HISTORY_ARG_SPECS)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def add_canon_toggle(parser: argparse.ArgumentParser) -> None:
|
|
113
|
+
"""Add option to disable canonical grammar."""
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--no-canon",
|
|
116
|
+
dest="grammar_canon",
|
|
117
|
+
action="store_false",
|
|
118
|
+
default=True,
|
|
119
|
+
help="Disable canonical grammar",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _add_run_parser(sub: argparse._SubParsersAction) -> None:
|
|
124
|
+
"""Configure the ``run`` subcommand."""
|
|
125
|
+
from .execution import cmd_run, DEFAULT_SUMMARY_SERIES_LIMIT
|
|
126
|
+
|
|
127
|
+
p_run = sub.add_parser(
|
|
128
|
+
"run",
|
|
129
|
+
help=(
|
|
130
|
+
"Run a free scenario or preset and optionally export history"
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
add_common_args(p_run)
|
|
134
|
+
p_run.add_argument("--steps", type=int, default=100)
|
|
135
|
+
add_canon_toggle(p_run)
|
|
136
|
+
add_grammar_selector_args(p_run)
|
|
137
|
+
add_history_export_args(p_run)
|
|
138
|
+
p_run.add_argument("--preset", type=str, default=None, help=_PRESET_HELP)
|
|
139
|
+
p_run.add_argument("--sequence-file", type=str, default=None)
|
|
140
|
+
p_run.add_argument("--summary", action="store_true")
|
|
141
|
+
p_run.add_argument(
|
|
142
|
+
"--summary-limit",
|
|
143
|
+
type=int,
|
|
144
|
+
default=DEFAULT_SUMMARY_SERIES_LIMIT,
|
|
145
|
+
help=(
|
|
146
|
+
"Maximum number of samples per series in the summary (<=0 to"
|
|
147
|
+
" disable trimming)"
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
p_run.set_defaults(func=cmd_run)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _add_sequence_parser(sub: argparse._SubParsersAction) -> None:
|
|
154
|
+
"""Configure the ``sequence`` subcommand."""
|
|
155
|
+
from .execution import cmd_sequence
|
|
156
|
+
|
|
157
|
+
p_seq = sub.add_parser(
|
|
158
|
+
"sequence",
|
|
159
|
+
help="Execute a sequence (preset or YAML/JSON)",
|
|
160
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
161
|
+
epilog=(
|
|
162
|
+
"JSON sequence example:\n"
|
|
163
|
+
"[\n"
|
|
164
|
+
' "A",\n'
|
|
165
|
+
' {"WAIT": 1},\n'
|
|
166
|
+
' {"THOL": {"body": ["A", {"WAIT": 2}], "repeat": 2}}\n'
|
|
167
|
+
"]"
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
add_common_args(p_seq)
|
|
171
|
+
p_seq.add_argument("--preset", type=str, default=None, help=_PRESET_HELP)
|
|
172
|
+
p_seq.add_argument("--sequence-file", type=str, default=None)
|
|
173
|
+
add_history_export_args(p_seq)
|
|
174
|
+
add_grammar_args(p_seq)
|
|
175
|
+
p_seq.set_defaults(func=cmd_sequence)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _add_metrics_parser(sub: argparse._SubParsersAction) -> None:
|
|
179
|
+
"""Configure the ``metrics`` subcommand."""
|
|
180
|
+
from .execution import cmd_metrics
|
|
181
|
+
|
|
182
|
+
p_met = sub.add_parser(
|
|
183
|
+
"metrics", help="Run briefly and export key metrics"
|
|
184
|
+
)
|
|
185
|
+
add_common_args(p_met)
|
|
186
|
+
p_met.add_argument("--steps", type=int, default=None)
|
|
187
|
+
add_canon_toggle(p_met)
|
|
188
|
+
add_grammar_selector_args(p_met)
|
|
189
|
+
p_met.add_argument("--save", type=str, default=None)
|
|
190
|
+
p_met.add_argument(
|
|
191
|
+
"--summary-limit",
|
|
192
|
+
type=int,
|
|
193
|
+
default=None,
|
|
194
|
+
help=(
|
|
195
|
+
"Maximum number of samples per series in the summary (<=0 to"
|
|
196
|
+
" disable trimming)"
|
|
197
|
+
),
|
|
198
|
+
)
|
|
199
|
+
p_met.set_defaults(func=cmd_metrics)
|
tnfr/cli/arguments.pyi
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Any, Iterable
|
|
5
|
+
|
|
6
|
+
from ..gamma import GAMMA_REGISTRY
|
|
7
|
+
from ..types import ArgSpec
|
|
8
|
+
from .utils import spec
|
|
9
|
+
|
|
10
|
+
GRAMMAR_ARG_SPECS: tuple[ArgSpec, ...]
|
|
11
|
+
HISTORY_ARG_SPECS: tuple[ArgSpec, ...]
|
|
12
|
+
COMMON_ARG_SPECS: tuple[ArgSpec, ...]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def add_arg_specs(parser: argparse._ActionsContainer, specs: Iterable[ArgSpec]) -> None: ...
|
|
16
|
+
|
|
17
|
+
def _args_to_dict(args: argparse.Namespace, prefix: str) -> dict[str, Any]: ...
|
|
18
|
+
|
|
19
|
+
def add_common_args(parser: argparse.ArgumentParser) -> None: ...
|
|
20
|
+
|
|
21
|
+
def add_grammar_args(parser: argparse.ArgumentParser) -> None: ...
|
|
22
|
+
|
|
23
|
+
def add_grammar_selector_args(parser: argparse.ArgumentParser) -> None: ...
|
|
24
|
+
|
|
25
|
+
def add_history_export_args(parser: argparse.ArgumentParser) -> None: ...
|
|
26
|
+
|
|
27
|
+
def add_canon_toggle(parser: argparse.ArgumentParser) -> None: ...
|
|
28
|
+
|
|
29
|
+
def _add_run_parser(sub: argparse._SubParsersAction) -> None: ...
|
|
30
|
+
|
|
31
|
+
def _add_sequence_parser(sub: argparse._SubParsersAction) -> None: ...
|
|
32
|
+
|
|
33
|
+
def _add_metrics_parser(sub: argparse._SubParsersAction) -> None: ...
|
tnfr/cli/execution.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
import networkx as nx
|
|
10
|
+
|
|
11
|
+
from ..constants import METRIC_DEFAULTS
|
|
12
|
+
from ..sense import register_sigma_callback
|
|
13
|
+
from ..metrics import (
|
|
14
|
+
register_metrics_callbacks,
|
|
15
|
+
glyph_top,
|
|
16
|
+
export_metrics,
|
|
17
|
+
build_metrics_summary,
|
|
18
|
+
)
|
|
19
|
+
from ..metrics.core import _metrics_step
|
|
20
|
+
from ..trace import register_trace
|
|
21
|
+
from ..execution import CANONICAL_PRESET_NAME, play
|
|
22
|
+
from ..dynamics import (
|
|
23
|
+
run,
|
|
24
|
+
default_glyph_selector,
|
|
25
|
+
parametric_glyph_selector,
|
|
26
|
+
validate_canon,
|
|
27
|
+
)
|
|
28
|
+
from ..config.presets import (
|
|
29
|
+
PREFERRED_PRESET_NAMES,
|
|
30
|
+
get_preset,
|
|
31
|
+
legacy_preset_guidance,
|
|
32
|
+
)
|
|
33
|
+
from ..config import apply_config
|
|
34
|
+
from ..io import read_structured_file, safe_write, StructuredFileError
|
|
35
|
+
from ..glyph_history import ensure_history
|
|
36
|
+
from ..ontosim import prepare_network
|
|
37
|
+
from ..types import ProgramTokens
|
|
38
|
+
from ..utils import get_logger, json_dumps
|
|
39
|
+
from ..flatten import parse_program_tokens
|
|
40
|
+
|
|
41
|
+
from .arguments import _args_to_dict
|
|
42
|
+
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# CLI summaries should remain concise by default while allowing callers to
|
|
47
|
+
# inspect the full glyphogram series when needed.
|
|
48
|
+
DEFAULT_SUMMARY_SERIES_LIMIT = 10
|
|
49
|
+
|
|
50
|
+
_PREFERRED_PRESETS_DISPLAY = ", ".join(PREFERRED_PRESET_NAMES)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _save_json(path: str, data: Any) -> None:
|
|
54
|
+
payload = json_dumps(data, ensure_ascii=False, indent=2, default=list)
|
|
55
|
+
safe_write(path, lambda f: f.write(payload))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _attach_callbacks(G: "nx.Graph") -> None:
|
|
59
|
+
register_sigma_callback(G)
|
|
60
|
+
register_metrics_callbacks(G)
|
|
61
|
+
register_trace(G)
|
|
62
|
+
_metrics_step(G, ctx=None)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _persist_history(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
66
|
+
if getattr(args, "save_history", None) or getattr(args, "export_history_base", None):
|
|
67
|
+
history = ensure_history(G)
|
|
68
|
+
if getattr(args, "save_history", None):
|
|
69
|
+
_save_json(args.save_history, history)
|
|
70
|
+
if getattr(args, "export_history_base", None):
|
|
71
|
+
export_metrics(G, args.export_history_base, fmt=args.export_format)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_basic_graph(args: argparse.Namespace) -> "nx.Graph":
|
|
75
|
+
n = args.nodes
|
|
76
|
+
topology = getattr(args, "topology", "ring").lower()
|
|
77
|
+
seed = getattr(args, "seed", None)
|
|
78
|
+
if topology == "ring":
|
|
79
|
+
G = nx.cycle_graph(n)
|
|
80
|
+
elif topology == "complete":
|
|
81
|
+
G = nx.complete_graph(n)
|
|
82
|
+
elif topology == "erdos":
|
|
83
|
+
if getattr(args, "p", None) is not None:
|
|
84
|
+
prob = float(args.p)
|
|
85
|
+
else:
|
|
86
|
+
if n <= 0:
|
|
87
|
+
fallback = 0.0
|
|
88
|
+
else:
|
|
89
|
+
fallback = 3.0 / n
|
|
90
|
+
prob = min(max(fallback, 0.0), 1.0)
|
|
91
|
+
if not 0.0 <= prob <= 1.0:
|
|
92
|
+
raise ValueError(f"p must be between 0 and 1; received {prob}")
|
|
93
|
+
G = nx.gnp_random_graph(n, prob, seed=seed)
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Invalid topology '{topology}'. Accepted options are: ring, complete, erdos"
|
|
97
|
+
)
|
|
98
|
+
if seed is not None:
|
|
99
|
+
G.graph["RANDOM_SEED"] = int(seed)
|
|
100
|
+
return G
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def apply_cli_config(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
104
|
+
if args.config:
|
|
105
|
+
apply_config(G, Path(args.config))
|
|
106
|
+
arg_map = {
|
|
107
|
+
"dt": ("DT", float),
|
|
108
|
+
"integrator": ("INTEGRATOR_METHOD", str),
|
|
109
|
+
"remesh_mode": ("REMESH_MODE", str),
|
|
110
|
+
"glyph_hysteresis_window": ("GLYPH_HYSTERESIS_WINDOW", int),
|
|
111
|
+
}
|
|
112
|
+
for attr, (key, conv) in arg_map.items():
|
|
113
|
+
val = getattr(args, attr, None)
|
|
114
|
+
if val is not None:
|
|
115
|
+
G.graph[key] = conv(val)
|
|
116
|
+
|
|
117
|
+
gcanon = {
|
|
118
|
+
**METRIC_DEFAULTS["GRAMMAR_CANON"],
|
|
119
|
+
**_args_to_dict(args, prefix="grammar_"),
|
|
120
|
+
}
|
|
121
|
+
if getattr(args, "grammar_canon", None) is not None:
|
|
122
|
+
gcanon["enabled"] = bool(args.grammar_canon)
|
|
123
|
+
G.graph["GRAMMAR_CANON"] = gcanon
|
|
124
|
+
|
|
125
|
+
selector = getattr(args, "selector", None)
|
|
126
|
+
if selector is not None:
|
|
127
|
+
sel_map = {
|
|
128
|
+
"basic": default_glyph_selector,
|
|
129
|
+
"param": parametric_glyph_selector,
|
|
130
|
+
}
|
|
131
|
+
G.graph["glyph_selector"] = sel_map.get(
|
|
132
|
+
selector, default_glyph_selector
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if hasattr(args, "gamma_type"):
|
|
136
|
+
G.graph["GAMMA"] = {
|
|
137
|
+
"type": args.gamma_type,
|
|
138
|
+
"beta": args.gamma_beta,
|
|
139
|
+
"R0": args.gamma_R0,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for attr, key in (
|
|
143
|
+
("trace_verbosity", "TRACE"),
|
|
144
|
+
("metrics_verbosity", "METRICS"),
|
|
145
|
+
):
|
|
146
|
+
cfg = G.graph.get(key)
|
|
147
|
+
if not isinstance(cfg, dict):
|
|
148
|
+
cfg = deepcopy(METRIC_DEFAULTS[key])
|
|
149
|
+
G.graph[key] = cfg
|
|
150
|
+
value = getattr(args, attr, None)
|
|
151
|
+
if value is not None:
|
|
152
|
+
cfg["verbosity"] = value
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def register_callbacks_and_observer(G: "nx.Graph") -> None:
|
|
156
|
+
_attach_callbacks(G)
|
|
157
|
+
validate_canon(G)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _build_graph_from_args(args: argparse.Namespace) -> "nx.Graph":
|
|
161
|
+
G = build_basic_graph(args)
|
|
162
|
+
apply_cli_config(G, args)
|
|
163
|
+
if getattr(args, "observer", False):
|
|
164
|
+
G.graph["ATTACH_STD_OBSERVER"] = True
|
|
165
|
+
prepare_network(G)
|
|
166
|
+
register_callbacks_and_observer(G)
|
|
167
|
+
return G
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _load_sequence(path: Path) -> ProgramTokens:
|
|
171
|
+
try:
|
|
172
|
+
data = read_structured_file(path)
|
|
173
|
+
except (StructuredFileError, OSError) as exc:
|
|
174
|
+
if isinstance(exc, StructuredFileError):
|
|
175
|
+
message = str(exc)
|
|
176
|
+
else:
|
|
177
|
+
message = str(StructuredFileError(path, exc))
|
|
178
|
+
logger.error("%s", message)
|
|
179
|
+
raise SystemExit(1) from exc
|
|
180
|
+
return parse_program_tokens(data)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def resolve_program(
|
|
184
|
+
args: argparse.Namespace, default: Optional[ProgramTokens] = None
|
|
185
|
+
) -> Optional[ProgramTokens]:
|
|
186
|
+
if getattr(args, "preset", None):
|
|
187
|
+
try:
|
|
188
|
+
return get_preset(args.preset)
|
|
189
|
+
except KeyError as exc:
|
|
190
|
+
guidance = legacy_preset_guidance(args.preset)
|
|
191
|
+
if guidance is not None:
|
|
192
|
+
details = guidance
|
|
193
|
+
else:
|
|
194
|
+
details = (
|
|
195
|
+
exc.args[0]
|
|
196
|
+
if exc.args
|
|
197
|
+
else "Legacy preset identifier rejected."
|
|
198
|
+
)
|
|
199
|
+
logger.error(
|
|
200
|
+
(
|
|
201
|
+
"Unknown preset '%s'. Available presets: %s. %s "
|
|
202
|
+
"Use --sequence-file to execute custom sequences."
|
|
203
|
+
),
|
|
204
|
+
args.preset,
|
|
205
|
+
_PREFERRED_PRESETS_DISPLAY,
|
|
206
|
+
details,
|
|
207
|
+
)
|
|
208
|
+
raise SystemExit(1) from exc
|
|
209
|
+
if getattr(args, "sequence_file", None):
|
|
210
|
+
return _load_sequence(Path(args.sequence_file))
|
|
211
|
+
return default
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def run_program(
|
|
215
|
+
G: Optional["nx.Graph"],
|
|
216
|
+
program: Optional[ProgramTokens],
|
|
217
|
+
args: argparse.Namespace,
|
|
218
|
+
) -> "nx.Graph":
|
|
219
|
+
if G is None:
|
|
220
|
+
G = _build_graph_from_args(args)
|
|
221
|
+
|
|
222
|
+
if program is None:
|
|
223
|
+
steps = getattr(args, "steps", 100)
|
|
224
|
+
steps = 100 if steps is None else int(steps)
|
|
225
|
+
if steps < 0:
|
|
226
|
+
steps = 0
|
|
227
|
+
|
|
228
|
+
run_kwargs: dict[str, Any] = {}
|
|
229
|
+
for attr in ("dt", "use_Si", "apply_glyphs"):
|
|
230
|
+
value = getattr(args, attr, None)
|
|
231
|
+
if value is not None:
|
|
232
|
+
run_kwargs[attr] = value
|
|
233
|
+
|
|
234
|
+
run(G, steps=steps, **run_kwargs)
|
|
235
|
+
else:
|
|
236
|
+
play(G, program)
|
|
237
|
+
|
|
238
|
+
_persist_history(G, args)
|
|
239
|
+
return G
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _run_cli_program(
|
|
243
|
+
args: argparse.Namespace,
|
|
244
|
+
*,
|
|
245
|
+
default_program: Optional[ProgramTokens] = None,
|
|
246
|
+
graph: Optional["nx.Graph"] = None,
|
|
247
|
+
) -> tuple[int, Optional["nx.Graph"]]:
|
|
248
|
+
try:
|
|
249
|
+
program = resolve_program(args, default=default_program)
|
|
250
|
+
except SystemExit as exc:
|
|
251
|
+
code = exc.code if isinstance(exc.code, int) else 1
|
|
252
|
+
return code or 1, None
|
|
253
|
+
|
|
254
|
+
result_graph = run_program(graph, program, args)
|
|
255
|
+
return 0, result_graph
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _log_run_summaries(G: "nx.Graph", args: argparse.Namespace) -> None:
|
|
259
|
+
cfg_coh = G.graph.get("COHERENCE", METRIC_DEFAULTS["COHERENCE"])
|
|
260
|
+
cfg_diag = G.graph.get("DIAGNOSIS", METRIC_DEFAULTS["DIAGNOSIS"])
|
|
261
|
+
hist = ensure_history(G)
|
|
262
|
+
|
|
263
|
+
if cfg_coh.get("enabled", True):
|
|
264
|
+
Wstats = hist.get(cfg_coh.get("stats_history_key", "W_stats"), [])
|
|
265
|
+
if Wstats:
|
|
266
|
+
logger.info("[COHERENCE] last step: %s", Wstats[-1])
|
|
267
|
+
|
|
268
|
+
if cfg_diag.get("enabled", True):
|
|
269
|
+
last_diag = hist.get(cfg_diag.get("history_key", "nodal_diag"), [])
|
|
270
|
+
if last_diag:
|
|
271
|
+
sample = list(last_diag[-1].values())[:3]
|
|
272
|
+
logger.info("[DIAGNOSIS] sample: %s", sample)
|
|
273
|
+
|
|
274
|
+
if args.summary:
|
|
275
|
+
summary_limit = getattr(args, "summary_limit", DEFAULT_SUMMARY_SERIES_LIMIT)
|
|
276
|
+
summary, has_latency_values = build_metrics_summary(
|
|
277
|
+
G, series_limit=summary_limit
|
|
278
|
+
)
|
|
279
|
+
logger.info("Global Tg: %s", summary["Tg_global"])
|
|
280
|
+
logger.info("Top operators by Tg: %s", glyph_top(G, k=5))
|
|
281
|
+
if has_latency_values:
|
|
282
|
+
logger.info("Average latency: %s", summary["latency_mean"])
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
286
|
+
code, graph = _run_cli_program(args)
|
|
287
|
+
if code != 0:
|
|
288
|
+
return code
|
|
289
|
+
|
|
290
|
+
if graph is not None:
|
|
291
|
+
_log_run_summaries(graph, args)
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def cmd_sequence(args: argparse.Namespace) -> int:
|
|
296
|
+
if args.preset and args.sequence_file:
|
|
297
|
+
logger.error(
|
|
298
|
+
"Cannot use --preset and --sequence-file at the same time"
|
|
299
|
+
)
|
|
300
|
+
return 1
|
|
301
|
+
code, _ = _run_cli_program(
|
|
302
|
+
args, default_program=get_preset(CANONICAL_PRESET_NAME)
|
|
303
|
+
)
|
|
304
|
+
return code
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def cmd_metrics(args: argparse.Namespace) -> int:
|
|
308
|
+
if getattr(args, "steps", None) is None:
|
|
309
|
+
# Default a longer run for metrics stability
|
|
310
|
+
args.steps = 200
|
|
311
|
+
|
|
312
|
+
code, graph = _run_cli_program(args)
|
|
313
|
+
if code != 0 or graph is None:
|
|
314
|
+
return code
|
|
315
|
+
|
|
316
|
+
summary_limit = getattr(args, "summary_limit", None)
|
|
317
|
+
out, _ = build_metrics_summary(graph, series_limit=summary_limit)
|
|
318
|
+
if args.save:
|
|
319
|
+
_save_json(args.save, out)
|
|
320
|
+
else:
|
|
321
|
+
logger.info("%s", json_dumps(out))
|
|
322
|
+
return 0
|