tnfr 3.0.3__py3-none-any.whl → 8.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tnfr might be problematic. Click here for more details.
- tnfr/__init__.py +375 -56
- tnfr/__init__.pyi +33 -0
- tnfr/_compat.py +10 -0
- tnfr/_generated_version.py +34 -0
- tnfr/_version.py +49 -0
- tnfr/_version.pyi +7 -0
- tnfr/alias.py +723 -0
- tnfr/alias.pyi +108 -0
- tnfr/backends/__init__.py +354 -0
- tnfr/backends/jax_backend.py +173 -0
- tnfr/backends/numpy_backend.py +238 -0
- tnfr/backends/optimized_numpy.py +420 -0
- tnfr/backends/torch_backend.py +408 -0
- tnfr/cache.py +171 -0
- tnfr/cache.pyi +13 -0
- tnfr/cli/__init__.py +110 -0
- tnfr/cli/__init__.pyi +26 -0
- tnfr/cli/arguments.py +489 -0
- tnfr/cli/arguments.pyi +29 -0
- tnfr/cli/execution.py +914 -0
- tnfr/cli/execution.pyi +70 -0
- tnfr/cli/interactive_validator.py +614 -0
- tnfr/cli/utils.py +51 -0
- tnfr/cli/utils.pyi +7 -0
- tnfr/cli/validate.py +236 -0
- tnfr/compat/__init__.py +85 -0
- tnfr/compat/dataclass.py +136 -0
- tnfr/compat/jsonschema_stub.py +61 -0
- tnfr/compat/matplotlib_stub.py +73 -0
- tnfr/compat/numpy_stub.py +155 -0
- tnfr/config/__init__.py +224 -0
- tnfr/config/__init__.pyi +10 -0
- tnfr/config/constants.py +104 -0
- tnfr/config/constants.pyi +12 -0
- tnfr/config/defaults.py +54 -0
- tnfr/config/defaults_core.py +212 -0
- tnfr/config/defaults_init.py +33 -0
- tnfr/config/defaults_metric.py +104 -0
- tnfr/config/feature_flags.py +81 -0
- tnfr/config/feature_flags.pyi +16 -0
- tnfr/config/glyph_constants.py +31 -0
- tnfr/config/init.py +77 -0
- tnfr/config/init.pyi +8 -0
- tnfr/config/operator_names.py +254 -0
- tnfr/config/operator_names.pyi +36 -0
- tnfr/config/physics_derivation.py +354 -0
- tnfr/config/presets.py +83 -0
- tnfr/config/presets.pyi +7 -0
- tnfr/config/security.py +927 -0
- tnfr/config/thresholds.py +114 -0
- tnfr/config/tnfr_config.py +498 -0
- tnfr/constants/__init__.py +92 -0
- tnfr/constants/__init__.pyi +92 -0
- tnfr/constants/aliases.py +33 -0
- tnfr/constants/aliases.pyi +27 -0
- tnfr/constants/init.py +33 -0
- tnfr/constants/init.pyi +12 -0
- tnfr/constants/metric.py +104 -0
- tnfr/constants/metric.pyi +19 -0
- tnfr/core/__init__.py +33 -0
- tnfr/core/container.py +226 -0
- tnfr/core/default_implementations.py +329 -0
- tnfr/core/interfaces.py +279 -0
- tnfr/dynamics/__init__.py +238 -0
- tnfr/dynamics/__init__.pyi +83 -0
- tnfr/dynamics/adaptation.py +267 -0
- tnfr/dynamics/adaptation.pyi +7 -0
- tnfr/dynamics/adaptive_sequences.py +189 -0
- tnfr/dynamics/adaptive_sequences.pyi +14 -0
- tnfr/dynamics/aliases.py +23 -0
- tnfr/dynamics/aliases.pyi +19 -0
- tnfr/dynamics/bifurcation.py +232 -0
- tnfr/dynamics/canonical.py +229 -0
- tnfr/dynamics/canonical.pyi +48 -0
- tnfr/dynamics/coordination.py +385 -0
- tnfr/dynamics/coordination.pyi +25 -0
- tnfr/dynamics/dnfr.py +3034 -0
- tnfr/dynamics/dnfr.pyi +26 -0
- tnfr/dynamics/dynamic_limits.py +225 -0
- tnfr/dynamics/feedback.py +252 -0
- tnfr/dynamics/feedback.pyi +24 -0
- tnfr/dynamics/fused_dnfr.py +454 -0
- tnfr/dynamics/homeostasis.py +157 -0
- tnfr/dynamics/homeostasis.pyi +14 -0
- tnfr/dynamics/integrators.py +661 -0
- tnfr/dynamics/integrators.pyi +36 -0
- tnfr/dynamics/learning.py +310 -0
- tnfr/dynamics/learning.pyi +33 -0
- tnfr/dynamics/metabolism.py +254 -0
- tnfr/dynamics/nbody.py +796 -0
- tnfr/dynamics/nbody_tnfr.py +783 -0
- tnfr/dynamics/propagation.py +326 -0
- tnfr/dynamics/runtime.py +908 -0
- tnfr/dynamics/runtime.pyi +77 -0
- tnfr/dynamics/sampling.py +36 -0
- tnfr/dynamics/sampling.pyi +7 -0
- tnfr/dynamics/selectors.py +711 -0
- tnfr/dynamics/selectors.pyi +85 -0
- tnfr/dynamics/structural_clip.py +207 -0
- tnfr/errors/__init__.py +37 -0
- tnfr/errors/contextual.py +492 -0
- tnfr/execution.py +223 -0
- tnfr/execution.pyi +45 -0
- tnfr/extensions/__init__.py +205 -0
- tnfr/extensions/__init__.pyi +18 -0
- tnfr/extensions/base.py +173 -0
- tnfr/extensions/base.pyi +35 -0
- tnfr/extensions/business/__init__.py +71 -0
- tnfr/extensions/business/__init__.pyi +11 -0
- tnfr/extensions/business/cookbook.py +88 -0
- tnfr/extensions/business/cookbook.pyi +8 -0
- tnfr/extensions/business/health_analyzers.py +202 -0
- tnfr/extensions/business/health_analyzers.pyi +9 -0
- tnfr/extensions/business/patterns.py +183 -0
- tnfr/extensions/business/patterns.pyi +8 -0
- tnfr/extensions/medical/__init__.py +73 -0
- tnfr/extensions/medical/__init__.pyi +11 -0
- tnfr/extensions/medical/cookbook.py +88 -0
- tnfr/extensions/medical/cookbook.pyi +8 -0
- tnfr/extensions/medical/health_analyzers.py +181 -0
- tnfr/extensions/medical/health_analyzers.pyi +9 -0
- tnfr/extensions/medical/patterns.py +163 -0
- tnfr/extensions/medical/patterns.pyi +8 -0
- tnfr/flatten.py +262 -0
- tnfr/flatten.pyi +21 -0
- tnfr/gamma.py +354 -0
- tnfr/gamma.pyi +36 -0
- tnfr/glyph_history.py +377 -0
- tnfr/glyph_history.pyi +35 -0
- tnfr/glyph_runtime.py +19 -0
- tnfr/glyph_runtime.pyi +8 -0
- tnfr/immutable.py +218 -0
- tnfr/immutable.pyi +36 -0
- tnfr/initialization.py +203 -0
- tnfr/initialization.pyi +65 -0
- tnfr/io.py +10 -0
- tnfr/io.pyi +13 -0
- tnfr/locking.py +37 -0
- tnfr/locking.pyi +7 -0
- tnfr/mathematics/__init__.py +79 -0
- tnfr/mathematics/backend.py +453 -0
- tnfr/mathematics/backend.pyi +99 -0
- tnfr/mathematics/dynamics.py +408 -0
- tnfr/mathematics/dynamics.pyi +90 -0
- tnfr/mathematics/epi.py +391 -0
- tnfr/mathematics/epi.pyi +65 -0
- tnfr/mathematics/generators.py +242 -0
- tnfr/mathematics/generators.pyi +29 -0
- tnfr/mathematics/metrics.py +119 -0
- tnfr/mathematics/metrics.pyi +16 -0
- tnfr/mathematics/operators.py +239 -0
- tnfr/mathematics/operators.pyi +59 -0
- tnfr/mathematics/operators_factory.py +124 -0
- tnfr/mathematics/operators_factory.pyi +11 -0
- tnfr/mathematics/projection.py +87 -0
- tnfr/mathematics/projection.pyi +33 -0
- tnfr/mathematics/runtime.py +182 -0
- tnfr/mathematics/runtime.pyi +64 -0
- tnfr/mathematics/spaces.py +256 -0
- tnfr/mathematics/spaces.pyi +83 -0
- tnfr/mathematics/transforms.py +305 -0
- tnfr/mathematics/transforms.pyi +62 -0
- tnfr/metrics/__init__.py +79 -0
- tnfr/metrics/__init__.pyi +20 -0
- tnfr/metrics/buffer_cache.py +163 -0
- tnfr/metrics/buffer_cache.pyi +24 -0
- tnfr/metrics/cache_utils.py +214 -0
- tnfr/metrics/coherence.py +2009 -0
- tnfr/metrics/coherence.pyi +129 -0
- tnfr/metrics/common.py +158 -0
- tnfr/metrics/common.pyi +35 -0
- tnfr/metrics/core.py +316 -0
- tnfr/metrics/core.pyi +13 -0
- tnfr/metrics/diagnosis.py +833 -0
- tnfr/metrics/diagnosis.pyi +86 -0
- tnfr/metrics/emergence.py +245 -0
- tnfr/metrics/export.py +179 -0
- tnfr/metrics/export.pyi +7 -0
- tnfr/metrics/glyph_timing.py +379 -0
- tnfr/metrics/glyph_timing.pyi +81 -0
- tnfr/metrics/learning_metrics.py +280 -0
- tnfr/metrics/learning_metrics.pyi +21 -0
- tnfr/metrics/phase_coherence.py +351 -0
- tnfr/metrics/phase_compatibility.py +349 -0
- tnfr/metrics/reporting.py +183 -0
- tnfr/metrics/reporting.pyi +25 -0
- tnfr/metrics/sense_index.py +1203 -0
- tnfr/metrics/sense_index.pyi +9 -0
- tnfr/metrics/trig.py +373 -0
- tnfr/metrics/trig.pyi +13 -0
- tnfr/metrics/trig_cache.py +233 -0
- tnfr/metrics/trig_cache.pyi +10 -0
- tnfr/multiscale/__init__.py +32 -0
- tnfr/multiscale/hierarchical.py +517 -0
- tnfr/node.py +763 -0
- tnfr/node.pyi +139 -0
- tnfr/observers.py +255 -130
- tnfr/observers.pyi +31 -0
- tnfr/ontosim.py +144 -137
- tnfr/ontosim.pyi +28 -0
- tnfr/operators/__init__.py +1672 -0
- tnfr/operators/__init__.pyi +31 -0
- tnfr/operators/algebra.py +277 -0
- tnfr/operators/canonical_patterns.py +420 -0
- tnfr/operators/cascade.py +267 -0
- tnfr/operators/cycle_detection.py +358 -0
- tnfr/operators/definitions.py +4108 -0
- tnfr/operators/definitions.pyi +78 -0
- tnfr/operators/grammar.py +1164 -0
- tnfr/operators/grammar.pyi +140 -0
- tnfr/operators/hamiltonian.py +710 -0
- tnfr/operators/health_analyzer.py +809 -0
- tnfr/operators/jitter.py +272 -0
- tnfr/operators/jitter.pyi +11 -0
- tnfr/operators/lifecycle.py +314 -0
- tnfr/operators/metabolism.py +618 -0
- tnfr/operators/metrics.py +2138 -0
- tnfr/operators/network_analysis/__init__.py +27 -0
- tnfr/operators/network_analysis/source_detection.py +186 -0
- tnfr/operators/nodal_equation.py +395 -0
- tnfr/operators/pattern_detection.py +660 -0
- tnfr/operators/patterns.py +669 -0
- tnfr/operators/postconditions/__init__.py +38 -0
- tnfr/operators/postconditions/mutation.py +236 -0
- tnfr/operators/preconditions/__init__.py +1226 -0
- tnfr/operators/preconditions/coherence.py +305 -0
- tnfr/operators/preconditions/dissonance.py +236 -0
- tnfr/operators/preconditions/emission.py +128 -0
- tnfr/operators/preconditions/mutation.py +580 -0
- tnfr/operators/preconditions/reception.py +125 -0
- tnfr/operators/preconditions/resonance.py +364 -0
- tnfr/operators/registry.py +74 -0
- tnfr/operators/registry.pyi +9 -0
- tnfr/operators/remesh.py +1809 -0
- tnfr/operators/remesh.pyi +26 -0
- tnfr/operators/structural_units.py +268 -0
- tnfr/operators/unified_grammar.py +105 -0
- tnfr/parallel/__init__.py +54 -0
- tnfr/parallel/auto_scaler.py +234 -0
- tnfr/parallel/distributed.py +384 -0
- tnfr/parallel/engine.py +238 -0
- tnfr/parallel/gpu_engine.py +420 -0
- tnfr/parallel/monitoring.py +248 -0
- tnfr/parallel/partitioner.py +459 -0
- tnfr/py.typed +0 -0
- tnfr/recipes/__init__.py +22 -0
- tnfr/recipes/cookbook.py +743 -0
- tnfr/rng.py +178 -0
- tnfr/rng.pyi +26 -0
- tnfr/schemas/__init__.py +8 -0
- tnfr/schemas/grammar.json +94 -0
- tnfr/sdk/__init__.py +107 -0
- tnfr/sdk/__init__.pyi +19 -0
- tnfr/sdk/adaptive_system.py +173 -0
- tnfr/sdk/adaptive_system.pyi +21 -0
- tnfr/sdk/builders.py +370 -0
- tnfr/sdk/builders.pyi +51 -0
- tnfr/sdk/fluent.py +1121 -0
- tnfr/sdk/fluent.pyi +74 -0
- tnfr/sdk/templates.py +342 -0
- tnfr/sdk/templates.pyi +41 -0
- tnfr/sdk/utils.py +341 -0
- tnfr/secure_config.py +46 -0
- tnfr/security/__init__.py +70 -0
- tnfr/security/database.py +514 -0
- tnfr/security/subprocess.py +503 -0
- tnfr/security/validation.py +290 -0
- tnfr/selector.py +247 -0
- tnfr/selector.pyi +19 -0
- tnfr/sense.py +378 -0
- tnfr/sense.pyi +23 -0
- tnfr/services/__init__.py +17 -0
- tnfr/services/orchestrator.py +325 -0
- tnfr/sparse/__init__.py +39 -0
- tnfr/sparse/representations.py +492 -0
- tnfr/structural.py +705 -0
- tnfr/structural.pyi +83 -0
- tnfr/telemetry/__init__.py +35 -0
- tnfr/telemetry/cache_metrics.py +226 -0
- tnfr/telemetry/cache_metrics.pyi +64 -0
- tnfr/telemetry/nu_f.py +422 -0
- tnfr/telemetry/nu_f.pyi +108 -0
- tnfr/telemetry/verbosity.py +36 -0
- tnfr/telemetry/verbosity.pyi +15 -0
- tnfr/tokens.py +58 -0
- tnfr/tokens.pyi +36 -0
- tnfr/tools/__init__.py +20 -0
- tnfr/tools/domain_templates.py +478 -0
- tnfr/tools/sequence_generator.py +846 -0
- tnfr/topology/__init__.py +13 -0
- tnfr/topology/asymmetry.py +151 -0
- tnfr/trace.py +543 -0
- tnfr/trace.pyi +42 -0
- tnfr/tutorials/__init__.py +38 -0
- tnfr/tutorials/autonomous_evolution.py +285 -0
- tnfr/tutorials/interactive.py +1576 -0
- tnfr/tutorials/structural_metabolism.py +238 -0
- tnfr/types.py +775 -0
- tnfr/types.pyi +357 -0
- tnfr/units.py +68 -0
- tnfr/units.pyi +13 -0
- tnfr/utils/__init__.py +282 -0
- tnfr/utils/__init__.pyi +215 -0
- tnfr/utils/cache.py +4223 -0
- tnfr/utils/cache.pyi +470 -0
- tnfr/utils/callbacks.py +375 -0
- tnfr/utils/callbacks.pyi +49 -0
- tnfr/utils/chunks.py +108 -0
- tnfr/utils/chunks.pyi +22 -0
- tnfr/utils/data.py +428 -0
- tnfr/utils/data.pyi +74 -0
- tnfr/utils/graph.py +85 -0
- tnfr/utils/graph.pyi +10 -0
- tnfr/utils/init.py +821 -0
- tnfr/utils/init.pyi +80 -0
- tnfr/utils/io.py +559 -0
- tnfr/utils/io.pyi +66 -0
- tnfr/utils/numeric.py +114 -0
- tnfr/utils/numeric.pyi +21 -0
- tnfr/validation/__init__.py +257 -0
- tnfr/validation/__init__.pyi +85 -0
- tnfr/validation/compatibility.py +460 -0
- tnfr/validation/compatibility.pyi +6 -0
- tnfr/validation/config.py +73 -0
- tnfr/validation/graph.py +139 -0
- tnfr/validation/graph.pyi +18 -0
- tnfr/validation/input_validation.py +755 -0
- tnfr/validation/invariants.py +712 -0
- tnfr/validation/rules.py +253 -0
- tnfr/validation/rules.pyi +44 -0
- tnfr/validation/runtime.py +279 -0
- tnfr/validation/runtime.pyi +28 -0
- tnfr/validation/sequence_validator.py +162 -0
- tnfr/validation/soft_filters.py +170 -0
- tnfr/validation/soft_filters.pyi +32 -0
- tnfr/validation/spectral.py +164 -0
- tnfr/validation/spectral.pyi +42 -0
- tnfr/validation/validator.py +1266 -0
- tnfr/validation/window.py +39 -0
- tnfr/validation/window.pyi +1 -0
- tnfr/visualization/__init__.py +98 -0
- tnfr/visualization/cascade_viz.py +256 -0
- tnfr/visualization/hierarchy.py +284 -0
- tnfr/visualization/sequence_plotter.py +784 -0
- tnfr/viz/__init__.py +60 -0
- tnfr/viz/matplotlib.py +278 -0
- tnfr/viz/matplotlib.pyi +35 -0
- tnfr-8.5.0.dist-info/METADATA +573 -0
- tnfr-8.5.0.dist-info/RECORD +353 -0
- tnfr-8.5.0.dist-info/entry_points.txt +3 -0
- tnfr-3.0.3.dist-info/licenses/LICENSE.txt → tnfr-8.5.0.dist-info/licenses/LICENSE.md +1 -1
- tnfr/constants.py +0 -183
- tnfr/dynamics.py +0 -543
- tnfr/helpers.py +0 -198
- tnfr/main.py +0 -37
- tnfr/operators.py +0 -296
- tnfr-3.0.3.dist-info/METADATA +0 -35
- tnfr-3.0.3.dist-info/RECORD +0 -13
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/WHEEL +0 -0
- {tnfr-3.0.3.dist-info → tnfr-8.5.0.dist-info}/top_level.txt +0 -0
tnfr/cli/utils.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Utilities for CLI modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..utils import normalize_optional_int
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def spec(opt: str, /, **kwargs: Any) -> tuple[str, dict[str, Any]]:
|
|
12
|
+
"""Create an argument specification pair.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
opt:
|
|
17
|
+
Option string to register, e.g. ``"--foo"``.
|
|
18
|
+
**kwargs:
|
|
19
|
+
Keyword arguments forwarded to
|
|
20
|
+
:meth:`argparse.ArgumentParser.add_argument`.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
tuple[str, dict[str, Any]]
|
|
25
|
+
A pair suitable for collecting into argument specification sequences.
|
|
26
|
+
If ``dest`` is not provided it is
|
|
27
|
+
derived from ``opt`` by stripping leading dashes and replacing dots and
|
|
28
|
+
hyphens with underscores. ``default`` defaults to ``None`` so missing
|
|
29
|
+
options can be filtered easily.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
kwargs = dict(kwargs)
|
|
33
|
+
kwargs.setdefault("dest", opt.lstrip("-").replace("-", "_").replace(".", "_"))
|
|
34
|
+
kwargs.setdefault("default", None)
|
|
35
|
+
return opt, kwargs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_cli_variants(values: Iterable[Any] | None) -> list[int | None]:
|
|
39
|
+
"""Return a stable list of integer/``None`` variants for the CLI options."""
|
|
40
|
+
|
|
41
|
+
if values is None:
|
|
42
|
+
return [None]
|
|
43
|
+
parsed: list[int | None] = []
|
|
44
|
+
seen: set[int | None] = set()
|
|
45
|
+
for raw in values:
|
|
46
|
+
coerced = normalize_optional_int(raw, strict=True)
|
|
47
|
+
if coerced in seen:
|
|
48
|
+
continue
|
|
49
|
+
seen.add(coerced)
|
|
50
|
+
parsed.append(coerced)
|
|
51
|
+
return parsed or [None]
|
tnfr/cli/utils.pyi
ADDED
tnfr/cli/validate.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""CLI tool for TNFR validation.
|
|
3
|
+
|
|
4
|
+
This tool provides two modes:
|
|
5
|
+
1. Graph validation: Validate TNFR graphs from files (original behavior)
|
|
6
|
+
2. Interactive sequence validation: User-friendly sequence validator (new)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
import networkx as nx
|
|
16
|
+
|
|
17
|
+
from ..validation import (
|
|
18
|
+
InvariantSeverity,
|
|
19
|
+
TNFRValidator,
|
|
20
|
+
configure_validation,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_graph(filepath: str) -> nx.Graph:
|
|
25
|
+
"""Load a graph from file.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
filepath : str
|
|
30
|
+
Path to graph file (supports .graphml, .gml, .json).
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
nx.Graph
|
|
35
|
+
Loaded graph.
|
|
36
|
+
"""
|
|
37
|
+
filepath_obj = Path(filepath)
|
|
38
|
+
|
|
39
|
+
if not filepath_obj.exists():
|
|
40
|
+
raise FileNotFoundError(f"Graph file not found: {filepath}")
|
|
41
|
+
|
|
42
|
+
# Determine format from extension
|
|
43
|
+
ext = filepath_obj.suffix.lower()
|
|
44
|
+
|
|
45
|
+
if ext == ".graphml":
|
|
46
|
+
return nx.read_graphml(filepath)
|
|
47
|
+
elif ext == ".gml":
|
|
48
|
+
return nx.read_gml(filepath)
|
|
49
|
+
elif ext == ".json":
|
|
50
|
+
with open(filepath, "r") as f:
|
|
51
|
+
data = json.load(f)
|
|
52
|
+
return nx.node_link_graph(data)
|
|
53
|
+
else:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
f"Unsupported graph format: {ext}. Use .graphml, .gml, or .json"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main():
|
|
60
|
+
"""Main CLI entry point."""
|
|
61
|
+
parser = argparse.ArgumentParser(
|
|
62
|
+
description="TNFR Validation Tool - Validate graphs or sequences",
|
|
63
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
64
|
+
epilog="""
|
|
65
|
+
Examples:
|
|
66
|
+
# Interactive sequence validator (NEW!)
|
|
67
|
+
tnfr-validate --interactive
|
|
68
|
+
tnfr-validate -i
|
|
69
|
+
|
|
70
|
+
# Validate a graph file
|
|
71
|
+
tnfr-validate graph.graphml
|
|
72
|
+
|
|
73
|
+
# Export to JSON
|
|
74
|
+
tnfr-validate graph.graphml --format json --output report.json
|
|
75
|
+
|
|
76
|
+
# Export to HTML
|
|
77
|
+
tnfr-validate graph.graphml --format html --output report.html
|
|
78
|
+
|
|
79
|
+
# Set minimum severity to WARNING
|
|
80
|
+
tnfr-validate graph.graphml --min-severity warning
|
|
81
|
+
|
|
82
|
+
# Enable caching for multiple validations
|
|
83
|
+
tnfr-validate graph.graphml --cache
|
|
84
|
+
""",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--interactive",
|
|
89
|
+
"-i",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="Launch interactive sequence validator",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"graph_file",
|
|
96
|
+
nargs="?",
|
|
97
|
+
help="Path to graph file (.graphml, .gml, or .json). Required unless using --interactive mode.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"--format",
|
|
102
|
+
choices=["text", "json", "html"],
|
|
103
|
+
default="text",
|
|
104
|
+
help="Output format (default: text)",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
"--output",
|
|
109
|
+
"-o",
|
|
110
|
+
help="Output file (default: stdout for text, or auto-generated for json/html)",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--min-severity",
|
|
115
|
+
choices=["info", "warning", "error", "critical"],
|
|
116
|
+
default="error",
|
|
117
|
+
help="Minimum severity to report (default: error)",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--cache", action="store_true", help="Enable validation result caching"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--no-semantic",
|
|
126
|
+
action="store_true",
|
|
127
|
+
help="Disable semantic sequence validation",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"--phase-threshold",
|
|
132
|
+
type=float,
|
|
133
|
+
help="Phase coupling threshold in radians (default: π/2)",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
|
137
|
+
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--seed",
|
|
140
|
+
type=int,
|
|
141
|
+
help="Random seed for interactive validator (deterministic generation)",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
args = parser.parse_args()
|
|
145
|
+
|
|
146
|
+
# Handle interactive mode
|
|
147
|
+
if args.interactive:
|
|
148
|
+
from .interactive_validator import run_interactive_validator
|
|
149
|
+
|
|
150
|
+
return run_interactive_validator(seed=args.seed)
|
|
151
|
+
|
|
152
|
+
# Require graph_file for non-interactive mode
|
|
153
|
+
if not args.graph_file:
|
|
154
|
+
parser.error("graph_file is required when not using --interactive mode")
|
|
155
|
+
|
|
156
|
+
# Configure validation
|
|
157
|
+
severity_map = {
|
|
158
|
+
"info": InvariantSeverity.INFO,
|
|
159
|
+
"warning": InvariantSeverity.WARNING,
|
|
160
|
+
"error": InvariantSeverity.ERROR,
|
|
161
|
+
"critical": InvariantSeverity.CRITICAL,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
configure_validation(
|
|
165
|
+
validate_invariants=True,
|
|
166
|
+
enable_semantic_validation=not args.no_semantic,
|
|
167
|
+
min_severity=severity_map[args.min_severity],
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if args.verbose:
|
|
171
|
+
print(f"Loading graph from: {args.graph_file}")
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# Load graph
|
|
175
|
+
graph = load_graph(args.graph_file)
|
|
176
|
+
|
|
177
|
+
if args.verbose:
|
|
178
|
+
print(
|
|
179
|
+
f"Graph loaded: {graph.number_of_nodes()} nodes, {graph.number_of_edges()} edges"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Create validator
|
|
183
|
+
if args.phase_threshold:
|
|
184
|
+
validator = TNFRValidator(phase_coupling_threshold=args.phase_threshold)
|
|
185
|
+
else:
|
|
186
|
+
validator = TNFRValidator()
|
|
187
|
+
|
|
188
|
+
if args.cache:
|
|
189
|
+
validator.enable_cache(True)
|
|
190
|
+
|
|
191
|
+
# Validate
|
|
192
|
+
if args.verbose:
|
|
193
|
+
print("Running TNFR validation...")
|
|
194
|
+
|
|
195
|
+
violations = validator.validate_graph(graph)
|
|
196
|
+
|
|
197
|
+
# Generate output
|
|
198
|
+
if args.format == "text":
|
|
199
|
+
output = validator.generate_report(violations)
|
|
200
|
+
elif args.format == "json":
|
|
201
|
+
output = validator.export_to_json(violations)
|
|
202
|
+
elif args.format == "html":
|
|
203
|
+
output = validator.export_to_html(violations)
|
|
204
|
+
|
|
205
|
+
# Write output
|
|
206
|
+
if args.output:
|
|
207
|
+
output_path = Path(args.output)
|
|
208
|
+
with open(output_path, "w") as f:
|
|
209
|
+
f.write(output)
|
|
210
|
+
print(f"Report written to: {output_path}")
|
|
211
|
+
else:
|
|
212
|
+
# Print to stdout
|
|
213
|
+
print(output)
|
|
214
|
+
|
|
215
|
+
# Exit code based on violations
|
|
216
|
+
if violations:
|
|
217
|
+
# Check if there are any ERROR or CRITICAL violations
|
|
218
|
+
has_errors = any(
|
|
219
|
+
v.severity in [InvariantSeverity.ERROR, InvariantSeverity.CRITICAL]
|
|
220
|
+
for v in violations
|
|
221
|
+
)
|
|
222
|
+
sys.exit(1 if has_errors else 0)
|
|
223
|
+
else:
|
|
224
|
+
sys.exit(0)
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
228
|
+
if args.verbose:
|
|
229
|
+
import traceback
|
|
230
|
+
|
|
231
|
+
traceback.print_exc()
|
|
232
|
+
sys.exit(2)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
main()
|
tnfr/compat/__init__.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Compatibility layer for optional dependencies.
|
|
2
|
+
|
|
3
|
+
This module provides lightweight stubs and fallback interfaces for optional
|
|
4
|
+
dependencies like numpy, matplotlib, and jsonschema. When these packages are
|
|
5
|
+
not installed, the stubs allow type checking and imports to succeed without
|
|
6
|
+
runtime errors, while maintaining TNFR semantic clarity.
|
|
7
|
+
|
|
8
|
+
TYPE_CHECKING guards
|
|
9
|
+
--------------------
|
|
10
|
+
Use ``if TYPE_CHECKING:`` blocks to import optional typing-only dependencies.
|
|
11
|
+
At runtime, fallback stubs are used instead.
|
|
12
|
+
|
|
13
|
+
Example::
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import numpy as np
|
|
18
|
+
else:
|
|
19
|
+
from tnfr.compat import numpy_stub as np
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"get_numpy_or_stub",
|
|
26
|
+
"get_matplotlib_or_stub",
|
|
27
|
+
"get_jsonschema_or_stub",
|
|
28
|
+
"numpy_stub",
|
|
29
|
+
"matplotlib_stub",
|
|
30
|
+
"jsonschema_stub",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
# Import stubs so they're available as module attributes
|
|
36
|
+
from . import numpy_stub, matplotlib_stub, jsonschema_stub
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_numpy_or_stub() -> Any:
|
|
40
|
+
"""Return numpy module if available, otherwise return a stub.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
module or stub
|
|
45
|
+
The actual numpy module when installed, or a minimal stub providing
|
|
46
|
+
basic type compatibility for type checking.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
import numpy
|
|
50
|
+
|
|
51
|
+
return numpy
|
|
52
|
+
except ImportError:
|
|
53
|
+
return numpy_stub
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_matplotlib_or_stub() -> Any:
|
|
57
|
+
"""Return matplotlib module if available, otherwise return a stub.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
module or stub
|
|
62
|
+
The actual matplotlib module when installed, or a minimal stub.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
import matplotlib
|
|
66
|
+
|
|
67
|
+
return matplotlib
|
|
68
|
+
except ImportError:
|
|
69
|
+
return matplotlib_stub
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_jsonschema_or_stub() -> Any:
|
|
73
|
+
"""Return jsonschema module if available, otherwise return a stub.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
module or stub
|
|
78
|
+
The actual jsonschema module when installed, or a minimal stub.
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
import jsonschema
|
|
82
|
+
|
|
83
|
+
return jsonschema
|
|
84
|
+
except ImportError:
|
|
85
|
+
return jsonschema_stub
|
tnfr/compat/dataclass.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Dataclass compatibility for Python 3.9+.
|
|
2
|
+
|
|
3
|
+
Provides a wrapper for @dataclass that conditionally applies ``slots=True``
|
|
4
|
+
only on Python 3.10+ where it is supported, maintaining compatibility with
|
|
5
|
+
Python 3.9.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass as _dataclass
|
|
10
|
+
from typing import Type, TypeVar, overload
|
|
11
|
+
|
|
12
|
+
_T = TypeVar("_T")
|
|
13
|
+
|
|
14
|
+
# Check if slots parameter is supported (Python 3.10+)
|
|
15
|
+
_SLOTS_SUPPORTED = sys.version_info >= (3, 10)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
def dataclass(
|
|
20
|
+
cls: Type[_T],
|
|
21
|
+
/,
|
|
22
|
+
*,
|
|
23
|
+
init: bool = True,
|
|
24
|
+
repr: bool = True,
|
|
25
|
+
eq: bool = True,
|
|
26
|
+
order: bool = False,
|
|
27
|
+
unsafe_hash: bool = False,
|
|
28
|
+
frozen: bool = False,
|
|
29
|
+
match_args: bool = True,
|
|
30
|
+
kw_only: bool = False,
|
|
31
|
+
slots: bool = False,
|
|
32
|
+
) -> Type[_T]: ...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@overload
|
|
36
|
+
def dataclass(
|
|
37
|
+
cls: None = None,
|
|
38
|
+
/,
|
|
39
|
+
*,
|
|
40
|
+
init: bool = True,
|
|
41
|
+
repr: bool = True,
|
|
42
|
+
eq: bool = True,
|
|
43
|
+
order: bool = False,
|
|
44
|
+
unsafe_hash: bool = False,
|
|
45
|
+
frozen: bool = False,
|
|
46
|
+
match_args: bool = True,
|
|
47
|
+
kw_only: bool = False,
|
|
48
|
+
slots: bool = False,
|
|
49
|
+
) -> Type[_T]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def dataclass(
|
|
53
|
+
cls=None,
|
|
54
|
+
/,
|
|
55
|
+
*,
|
|
56
|
+
init=True,
|
|
57
|
+
repr=True,
|
|
58
|
+
eq=True,
|
|
59
|
+
order=False,
|
|
60
|
+
unsafe_hash=False,
|
|
61
|
+
frozen=False,
|
|
62
|
+
match_args=True,
|
|
63
|
+
kw_only=False,
|
|
64
|
+
slots=False,
|
|
65
|
+
):
|
|
66
|
+
"""Compatibility wrapper for @dataclass supporting Python 3.9+.
|
|
67
|
+
|
|
68
|
+
On Python 3.10+, passes all parameters including ``slots`` to dataclass.
|
|
69
|
+
On Python 3.9, silently ignores ``slots`` parameter since it's not supported.
|
|
70
|
+
|
|
71
|
+
This allows code to use ``slots=True`` for performance benefits on newer
|
|
72
|
+
Python versions while maintaining backward compatibility with 3.9.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
cls : type, optional
|
|
77
|
+
Class to decorate (when used without parentheses).
|
|
78
|
+
init : bool, default True
|
|
79
|
+
Generate __init__ method.
|
|
80
|
+
repr : bool, default True
|
|
81
|
+
Generate __repr__ method.
|
|
82
|
+
eq : bool, default True
|
|
83
|
+
Generate __eq__ method.
|
|
84
|
+
order : bool, default False
|
|
85
|
+
Generate ordering methods.
|
|
86
|
+
unsafe_hash : bool, default False
|
|
87
|
+
Force generation of __hash__ method.
|
|
88
|
+
frozen : bool, default False
|
|
89
|
+
Make instances immutable.
|
|
90
|
+
match_args : bool, default True
|
|
91
|
+
Generate __match_args__ for pattern matching (Python 3.10+).
|
|
92
|
+
kw_only : bool, default False
|
|
93
|
+
Make all fields keyword-only.
|
|
94
|
+
slots : bool, default False
|
|
95
|
+
Generate __slots__ (Python 3.10+ only, ignored on 3.9).
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
type
|
|
100
|
+
Decorated dataclass.
|
|
101
|
+
|
|
102
|
+
Examples
|
|
103
|
+
--------
|
|
104
|
+
>>> from tnfr.compat.dataclass import dataclass
|
|
105
|
+
>>> @dataclass(slots=True) # Works on both 3.9 and 3.10+
|
|
106
|
+
... class Point:
|
|
107
|
+
... x: float
|
|
108
|
+
... y: float
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
# Build kwargs based on Python version
|
|
112
|
+
kwargs = {
|
|
113
|
+
"init": init,
|
|
114
|
+
"repr": repr,
|
|
115
|
+
"eq": eq,
|
|
116
|
+
"order": order,
|
|
117
|
+
"unsafe_hash": unsafe_hash,
|
|
118
|
+
"frozen": frozen,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Add parameters supported in Python 3.10+
|
|
122
|
+
if sys.version_info >= (3, 10):
|
|
123
|
+
kwargs["match_args"] = match_args
|
|
124
|
+
kwargs["kw_only"] = kw_only
|
|
125
|
+
kwargs["slots"] = slots
|
|
126
|
+
|
|
127
|
+
# Handle decorator with and without parentheses
|
|
128
|
+
def wrap(c):
|
|
129
|
+
return _dataclass(c, **kwargs)
|
|
130
|
+
|
|
131
|
+
if cls is None:
|
|
132
|
+
# Called with parentheses: @dataclass(...)
|
|
133
|
+
return wrap
|
|
134
|
+
else:
|
|
135
|
+
# Called without parentheses: @dataclass
|
|
136
|
+
return wrap(cls)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Lightweight stub for jsonschema when it's not installed.
|
|
2
|
+
|
|
3
|
+
This stub provides minimal type compatibility for jsonschema when it's not
|
|
4
|
+
installed, allowing type checking to succeed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
__all__ = ["Draft7Validator", "exceptions"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _NotInstalledError(RuntimeError):
|
|
15
|
+
"""Raised when trying to use jsonschema operations without jsonschema installed."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, operation: str = "jsonschema operation") -> None:
|
|
18
|
+
super().__init__(
|
|
19
|
+
f"Cannot perform {operation}: jsonschema is not installed. "
|
|
20
|
+
"Install it with: pip install jsonschema"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SchemaError(Exception):
|
|
25
|
+
"""Stub for jsonschema.exceptions.SchemaError."""
|
|
26
|
+
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ValidationError(Exception):
|
|
31
|
+
"""Stub for jsonschema.exceptions.ValidationError."""
|
|
32
|
+
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _ExceptionsStub:
|
|
37
|
+
"""Stub for jsonschema.exceptions module."""
|
|
38
|
+
|
|
39
|
+
SchemaError = SchemaError
|
|
40
|
+
ValidationError = ValidationError
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Draft7Validator:
|
|
44
|
+
"""Stub for jsonschema.Draft7Validator."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
47
|
+
raise _NotInstalledError("Draft7Validator creation")
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def check_schema(cls, schema: Any) -> None:
|
|
51
|
+
raise _NotInstalledError("Draft7Validator.check_schema")
|
|
52
|
+
|
|
53
|
+
def validate(self, instance: Any) -> None:
|
|
54
|
+
raise _NotInstalledError("Draft7Validator.validate")
|
|
55
|
+
|
|
56
|
+
def iter_errors(self, instance: Any) -> Any:
|
|
57
|
+
raise _NotInstalledError("Draft7Validator.iter_errors")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Module-level stubs
|
|
61
|
+
exceptions = _ExceptionsStub()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Lightweight stub for matplotlib when it's not installed.
|
|
2
|
+
|
|
3
|
+
This stub provides minimal type compatibility for matplotlib when it's not
|
|
4
|
+
installed, allowing type checking to succeed.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
__all__ = ["pyplot", "axes", "figure"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _NotInstalledError(RuntimeError):
|
|
15
|
+
"""Raised when trying to use matplotlib operations without matplotlib installed."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, operation: str = "matplotlib operation") -> None:
|
|
18
|
+
super().__init__(
|
|
19
|
+
f"Cannot perform {operation}: matplotlib is not installed. "
|
|
20
|
+
"Install it with: pip install tnfr[viz] or pip install matplotlib"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Axes:
|
|
25
|
+
"""Stub for matplotlib.axes.Axes."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
28
|
+
raise _NotInstalledError("Axes creation")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Figure:
|
|
32
|
+
"""Stub for matplotlib.figure.Figure."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
35
|
+
raise _NotInstalledError("Figure creation")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _PyPlotStub:
|
|
39
|
+
"""Stub for matplotlib.pyplot module."""
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def subplots(*args: Any, **kwargs: Any) -> tuple[Figure, Axes]:
|
|
43
|
+
raise _NotInstalledError("pyplot.subplots")
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def savefig(*args: Any, **kwargs: Any) -> None:
|
|
47
|
+
raise _NotInstalledError("pyplot.savefig")
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def show(*args: Any, **kwargs: Any) -> None:
|
|
51
|
+
raise _NotInstalledError("pyplot.show")
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def figure(*args: Any, **kwargs: Any) -> Figure:
|
|
55
|
+
raise _NotInstalledError("pyplot.figure")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _AxesStub:
|
|
59
|
+
"""Stub for matplotlib.axes module."""
|
|
60
|
+
|
|
61
|
+
Axes = Axes
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _FigureStub:
|
|
65
|
+
"""Stub for matplotlib.figure module."""
|
|
66
|
+
|
|
67
|
+
Figure = Figure
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Module-level stubs
|
|
71
|
+
pyplot = _PyPlotStub()
|
|
72
|
+
axes = _AxesStub()
|
|
73
|
+
figure = _FigureStub()
|