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
|
@@ -0,0 +1,783 @@
|
|
|
1
|
+
"""N-body dynamics using pure TNFR physics (no external potentials).
|
|
2
|
+
|
|
3
|
+
This module implements N-body dynamics derived STRICTLY from TNFR structural
|
|
4
|
+
framework, without assuming any classical potentials (Newtonian, Coulomb, etc.).
|
|
5
|
+
|
|
6
|
+
Key Differences from Classical N-Body
|
|
7
|
+
--------------------------------------
|
|
8
|
+
|
|
9
|
+
**Classical Approach** (nbody.py):
|
|
10
|
+
- Assumes gravitational potential: U = -G*m*m/r
|
|
11
|
+
- Force computed as: F = -∇U
|
|
12
|
+
- ΔNFR = F/m (external assumption)
|
|
13
|
+
|
|
14
|
+
**TNFR Approach** (this module):
|
|
15
|
+
- NO assumed potential
|
|
16
|
+
- Coherence potential emerges from network structure
|
|
17
|
+
- ΔNFR computed from Hamiltonian commutator: ΔNFR = i[H_int, ·]/ℏ_str
|
|
18
|
+
- Attraction/repulsion emerges from phase synchronization and coupling
|
|
19
|
+
|
|
20
|
+
Theoretical Foundation
|
|
21
|
+
----------------------
|
|
22
|
+
|
|
23
|
+
The nodal equation:
|
|
24
|
+
∂EPI/∂t = νf · ΔNFR(t)
|
|
25
|
+
|
|
26
|
+
Where ΔNFR emerges from the internal Hamiltonian:
|
|
27
|
+
H_int = H_coh + H_freq + H_coupling
|
|
28
|
+
|
|
29
|
+
Components:
|
|
30
|
+
1. **H_coh**: Coherence potential from structural similarity (phase, EPI, νf, Si)
|
|
31
|
+
2. **H_freq**: Diagonal operator encoding each node's νf
|
|
32
|
+
3. **H_coupling**: Network topology-induced interactions
|
|
33
|
+
|
|
34
|
+
The crucial insight: Attractive forces emerge naturally from maximizing
|
|
35
|
+
coherence between nodes, NOT from assuming gravity.
|
|
36
|
+
|
|
37
|
+
Phase-Dependent Interaction
|
|
38
|
+
----------------------------
|
|
39
|
+
|
|
40
|
+
Unlike classical gravity (always attractive), TNFR coupling depends on phase:
|
|
41
|
+
- |φᵢ - φⱼ| small → strong coherence → attraction
|
|
42
|
+
- |φᵢ - φⱼ| ≈ π → destructive interference → repulsion
|
|
43
|
+
|
|
44
|
+
This captures wave-like behavior absent in classical mechanics.
|
|
45
|
+
|
|
46
|
+
Emergence of Classical Limit
|
|
47
|
+
-----------------------------
|
|
48
|
+
|
|
49
|
+
In the low-dissonance limit (ε → 0) with:
|
|
50
|
+
- Nearly synchronized phases: |φᵢ - φⱼ| → 0
|
|
51
|
+
- Strong coupling: all nodes connected
|
|
52
|
+
- High coherence: C(t) ≈ 1
|
|
53
|
+
|
|
54
|
+
The TNFR dynamics reproduces classical-like behavior, but from first principles.
|
|
55
|
+
|
|
56
|
+
References
|
|
57
|
+
----------
|
|
58
|
+
- TNFR.pdf § 2.3: Nodal equation
|
|
59
|
+
- src/tnfr/operators/hamiltonian.py: H_int construction
|
|
60
|
+
- docs/source/theory/07_emergence_classical_mechanics.md: Classical limit
|
|
61
|
+
- AGENTS.md § Canonical Invariants: TNFR physics principles
|
|
62
|
+
|
|
63
|
+
Examples
|
|
64
|
+
--------
|
|
65
|
+
Two-body orbital resonance (no gravitational assumption):
|
|
66
|
+
|
|
67
|
+
>>> from tnfr.dynamics.nbody_tnfr import TNFRNBodySystem
|
|
68
|
+
>>> import numpy as np
|
|
69
|
+
>>>
|
|
70
|
+
>>> # Create 2-body system
|
|
71
|
+
>>> system = TNFRNBodySystem(
|
|
72
|
+
... n_bodies=2,
|
|
73
|
+
... masses=[1.0, 0.1], # Actually νf^-1
|
|
74
|
+
... positions=np.array([[0, 0, 0], [1, 0, 0]]),
|
|
75
|
+
... velocities=np.array([[0, 0, 0], [0, 1, 0]]),
|
|
76
|
+
... phases=np.array([0.0, 0.0]) # Synchronized initially
|
|
77
|
+
... )
|
|
78
|
+
>>>
|
|
79
|
+
>>> # Evolve via pure TNFR dynamics
|
|
80
|
+
>>> history = system.evolve(t_final=10.0, dt=0.01)
|
|
81
|
+
>>>
|
|
82
|
+
>>> # Check that orbital behavior emerges without assuming gravity
|
|
83
|
+
>>> print(f"Energy conservation: {history['energy_drift']:.2e}")
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
from __future__ import annotations
|
|
87
|
+
|
|
88
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Optional
|
|
89
|
+
|
|
90
|
+
import numpy as np
|
|
91
|
+
from numpy.typing import NDArray
|
|
92
|
+
|
|
93
|
+
from ..structural import create_nfr
|
|
94
|
+
from ..types import TNFRGraph
|
|
95
|
+
from ..operators.hamiltonian import InternalHamiltonian
|
|
96
|
+
from ..alias import get_attr
|
|
97
|
+
|
|
98
|
+
if TYPE_CHECKING:
|
|
99
|
+
from matplotlib.figure import Figure
|
|
100
|
+
|
|
101
|
+
__all__ = (
|
|
102
|
+
"TNFRNBodySystem",
|
|
103
|
+
"compute_tnfr_coherence_potential",
|
|
104
|
+
"compute_tnfr_delta_nfr",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def compute_tnfr_coherence_potential(
|
|
109
|
+
G: TNFRGraph,
|
|
110
|
+
positions: NDArray[np.floating],
|
|
111
|
+
hbar_str: float = 1.0,
|
|
112
|
+
) -> float:
|
|
113
|
+
"""Compute coherence potential from TNFR network structure.
|
|
114
|
+
|
|
115
|
+
This is the pure TNFR potential - NO classical assumptions.
|
|
116
|
+
|
|
117
|
+
The potential emerges from:
|
|
118
|
+
- Structural similarity (coherence matrix)
|
|
119
|
+
- Network coupling topology
|
|
120
|
+
- Phase synchronization
|
|
121
|
+
|
|
122
|
+
NOT from Newtonian gravity or any other classical force law.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
G : TNFRGraph
|
|
127
|
+
Network graph with nodes containing TNFR attributes
|
|
128
|
+
positions : ndarray, shape (N, 3)
|
|
129
|
+
Current positions (affect phase evolution, not potential directly)
|
|
130
|
+
hbar_str : float, default=1.0
|
|
131
|
+
Structural Planck constant
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
U : float
|
|
136
|
+
Coherence potential energy (lower = more stable)
|
|
137
|
+
|
|
138
|
+
Notes
|
|
139
|
+
-----
|
|
140
|
+
In TNFR, the potential U encodes structural stability landscape.
|
|
141
|
+
Nodes evolve toward configurations that maximize coherence (minimize U).
|
|
142
|
+
"""
|
|
143
|
+
# Build internal Hamiltonian
|
|
144
|
+
ham = InternalHamiltonian(G, hbar_str=hbar_str)
|
|
145
|
+
|
|
146
|
+
# Potential is encoded in ground state energy
|
|
147
|
+
eigenvalues, _ = ham.get_spectrum()
|
|
148
|
+
|
|
149
|
+
# Total potential: sum of eigenvalues (trace of H_int)
|
|
150
|
+
# For energy conservation, we use ground state as reference
|
|
151
|
+
U = float(eigenvalues[0]) # Ground state energy
|
|
152
|
+
|
|
153
|
+
return U
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def compute_tnfr_delta_nfr(
|
|
157
|
+
G: TNFRGraph,
|
|
158
|
+
node_ids: List[str],
|
|
159
|
+
hbar_str: float = 1.0,
|
|
160
|
+
) -> NDArray[np.floating]:
|
|
161
|
+
"""Compute ΔNFR from Hamiltonian commutator (pure TNFR).
|
|
162
|
+
|
|
163
|
+
This is the correct TNFR computation of ΔNFR:
|
|
164
|
+
ΔNFR = i[H_int, ·]/ℏ_str
|
|
165
|
+
|
|
166
|
+
NOT from classical forces: F = -∇U (external assumption).
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
G : TNFRGraph
|
|
171
|
+
Network graph with TNFR attributes
|
|
172
|
+
node_ids : list of str
|
|
173
|
+
Node identifiers in order
|
|
174
|
+
hbar_str : float, default=1.0
|
|
175
|
+
Structural Planck constant
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
dnfr : ndarray, shape (N,)
|
|
180
|
+
ΔNFR values for each node (structural reorganization pressure)
|
|
181
|
+
|
|
182
|
+
Notes
|
|
183
|
+
-----
|
|
184
|
+
The ΔNFR values represent the "reorganization pressure" driving
|
|
185
|
+
structural evolution via the nodal equation: ∂EPI/∂t = νf · ΔNFR
|
|
186
|
+
"""
|
|
187
|
+
# Build Hamiltonian
|
|
188
|
+
ham = InternalHamiltonian(G, hbar_str=hbar_str)
|
|
189
|
+
|
|
190
|
+
# Compute ΔNFR for each node
|
|
191
|
+
dnfr = np.zeros(len(node_ids))
|
|
192
|
+
for i, node_id in enumerate(node_ids):
|
|
193
|
+
dnfr[i] = ham.compute_node_delta_nfr(node_id)
|
|
194
|
+
|
|
195
|
+
return dnfr
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class TNFRNBodySystem:
|
|
199
|
+
"""N-body system using pure TNFR physics (no classical assumptions).
|
|
200
|
+
|
|
201
|
+
This implementation computes dynamics from TNFR structural coherence,
|
|
202
|
+
without assuming any classical potentials (gravity, Coulomb, etc.).
|
|
203
|
+
|
|
204
|
+
Attributes
|
|
205
|
+
----------
|
|
206
|
+
n_bodies : int
|
|
207
|
+
Number of bodies
|
|
208
|
+
masses : ndarray, shape (N,)
|
|
209
|
+
Masses (m = 1/νf, structural inertia)
|
|
210
|
+
positions : ndarray, shape (N, 3)
|
|
211
|
+
Current positions
|
|
212
|
+
velocities : ndarray, shape (N, 3)
|
|
213
|
+
Current velocities
|
|
214
|
+
phases : ndarray, shape (N,)
|
|
215
|
+
Current phases (θ ∈ [0, 2π])
|
|
216
|
+
time : float
|
|
217
|
+
Current structural time
|
|
218
|
+
graph : TNFRGraph
|
|
219
|
+
TNFR network representation
|
|
220
|
+
hbar_str : float
|
|
221
|
+
Structural Planck constant
|
|
222
|
+
|
|
223
|
+
Notes
|
|
224
|
+
-----
|
|
225
|
+
Dynamics follow from nodal equation: ∂EPI/∂t = νf · ΔNFR
|
|
226
|
+
where ΔNFR is computed from Hamiltonian commutator.
|
|
227
|
+
|
|
228
|
+
Attraction/repulsion emerges from phase synchronization,
|
|
229
|
+
NOT from assumed gravitational potential.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(
|
|
233
|
+
self,
|
|
234
|
+
n_bodies: int,
|
|
235
|
+
masses: List[float] | NDArray[np.floating],
|
|
236
|
+
positions: NDArray[np.floating],
|
|
237
|
+
velocities: NDArray[np.floating],
|
|
238
|
+
phases: NDArray[np.floating] | None = None,
|
|
239
|
+
hbar_str: float = 1.0,
|
|
240
|
+
coupling_strength: float = 0.1,
|
|
241
|
+
coherence_strength: float = -1.0,
|
|
242
|
+
):
|
|
243
|
+
"""Initialize TNFR N-body system.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
n_bodies : int
|
|
248
|
+
Number of bodies
|
|
249
|
+
masses : array_like, shape (N,)
|
|
250
|
+
Masses (m = 1/νf, must be positive)
|
|
251
|
+
positions : ndarray, shape (N, 3)
|
|
252
|
+
Initial positions
|
|
253
|
+
velocities : ndarray, shape (N, 3)
|
|
254
|
+
Initial velocities
|
|
255
|
+
phases : ndarray, shape (N,), optional
|
|
256
|
+
Initial phases. If None, initialized to zero (synchronized)
|
|
257
|
+
hbar_str : float, default=1.0
|
|
258
|
+
Structural Planck constant
|
|
259
|
+
coupling_strength : float, default=0.1
|
|
260
|
+
Network coupling strength (J_0 in H_coupling)
|
|
261
|
+
coherence_strength : float, default=-1.0
|
|
262
|
+
Coherence potential strength (C_0 in H_coh)
|
|
263
|
+
Negative = attractive potential well
|
|
264
|
+
|
|
265
|
+
Raises
|
|
266
|
+
------
|
|
267
|
+
ValueError
|
|
268
|
+
If dimensions mismatch or masses non-positive
|
|
269
|
+
"""
|
|
270
|
+
if n_bodies < 1:
|
|
271
|
+
raise ValueError(f"n_bodies must be >= 1, got {n_bodies}")
|
|
272
|
+
|
|
273
|
+
self.n_bodies = n_bodies
|
|
274
|
+
self.masses = np.array(masses, dtype=float)
|
|
275
|
+
|
|
276
|
+
if len(self.masses) != n_bodies:
|
|
277
|
+
raise ValueError(
|
|
278
|
+
f"masses length {len(self.masses)} != n_bodies {n_bodies}"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if np.any(self.masses <= 0):
|
|
282
|
+
raise ValueError("All masses must be positive")
|
|
283
|
+
|
|
284
|
+
# State vectors
|
|
285
|
+
self.positions = np.asarray(positions, dtype=float).copy()
|
|
286
|
+
self.velocities = np.asarray(velocities, dtype=float).copy()
|
|
287
|
+
|
|
288
|
+
if phases is None:
|
|
289
|
+
self.phases = np.zeros(n_bodies, dtype=float)
|
|
290
|
+
else:
|
|
291
|
+
self.phases = np.asarray(phases, dtype=float).copy()
|
|
292
|
+
|
|
293
|
+
# Validate shapes
|
|
294
|
+
expected_shape = (n_bodies, 3)
|
|
295
|
+
if self.positions.shape != expected_shape:
|
|
296
|
+
raise ValueError(
|
|
297
|
+
f"positions shape {self.positions.shape} != {expected_shape}"
|
|
298
|
+
)
|
|
299
|
+
if self.velocities.shape != expected_shape:
|
|
300
|
+
raise ValueError(
|
|
301
|
+
f"velocities shape {self.velocities.shape} != {expected_shape}"
|
|
302
|
+
)
|
|
303
|
+
if self.phases.shape != (n_bodies,):
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"phases shape {self.phases.shape} != ({n_bodies},)"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
self.time = 0.0
|
|
309
|
+
self.hbar_str = float(hbar_str)
|
|
310
|
+
|
|
311
|
+
# TNFR parameters
|
|
312
|
+
self.coupling_strength = float(coupling_strength)
|
|
313
|
+
self.coherence_strength = float(coherence_strength)
|
|
314
|
+
|
|
315
|
+
# Build TNFR graph
|
|
316
|
+
self._build_graph()
|
|
317
|
+
|
|
318
|
+
def _build_graph(self) -> None:
|
|
319
|
+
"""Build TNFR graph representation.
|
|
320
|
+
|
|
321
|
+
Each body becomes a resonant node with:
|
|
322
|
+
- νf = 1/m (structural frequency)
|
|
323
|
+
- EPI encoding (position, velocity)
|
|
324
|
+
- Phase θ
|
|
325
|
+
- All-to-all coupling (full network)
|
|
326
|
+
"""
|
|
327
|
+
import networkx as nx
|
|
328
|
+
|
|
329
|
+
self.graph: TNFRGraph = nx.Graph()
|
|
330
|
+
self.graph.graph["name"] = "tnfr_nbody_system"
|
|
331
|
+
self.graph.graph["H_COUPLING_STRENGTH"] = self.coupling_strength
|
|
332
|
+
self.graph.graph["H_COH_STRENGTH"] = self.coherence_strength
|
|
333
|
+
|
|
334
|
+
# Add nodes with TNFR attributes
|
|
335
|
+
for i in range(self.n_bodies):
|
|
336
|
+
node_id = f"body_{i}"
|
|
337
|
+
|
|
338
|
+
# Structural frequency: νf = 1/m
|
|
339
|
+
nu_f = 1.0 / self.masses[i]
|
|
340
|
+
|
|
341
|
+
# Create NFR node with structured EPI
|
|
342
|
+
epi_state = {
|
|
343
|
+
"position": self.positions[i].copy(),
|
|
344
|
+
"velocity": self.velocities[i].copy(),
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# Note: create_nfr expects scalar epi for initialization
|
|
348
|
+
# We'll override it immediately
|
|
349
|
+
_, _ = create_nfr(
|
|
350
|
+
node_id,
|
|
351
|
+
epi=1.0, # Temporary, will be overwritten
|
|
352
|
+
vf=nu_f,
|
|
353
|
+
theta=float(self.phases[i]),
|
|
354
|
+
graph=self.graph,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Override with structured EPI
|
|
358
|
+
self.graph.nodes[node_id]["epi"] = epi_state
|
|
359
|
+
|
|
360
|
+
# Add edges (all-to-all coupling)
|
|
361
|
+
# In TNFR, coupling strength depends on structural similarity
|
|
362
|
+
# Here we use uniform coupling for simplicity
|
|
363
|
+
for i in range(self.n_bodies):
|
|
364
|
+
for j in range(i + 1, self.n_bodies):
|
|
365
|
+
node_i = f"body_{i}"
|
|
366
|
+
node_j = f"body_{j}"
|
|
367
|
+
|
|
368
|
+
# Edge weight: coupling strength
|
|
369
|
+
# (In more sophisticated version, could depend on distance)
|
|
370
|
+
weight = self.coupling_strength
|
|
371
|
+
self.graph.add_edge(node_i, node_j, weight=weight)
|
|
372
|
+
|
|
373
|
+
def compute_energy(self) -> Tuple[float, float, float]:
|
|
374
|
+
"""Compute system energy (kinetic + coherence potential).
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
kinetic : float
|
|
379
|
+
Kinetic energy K = Σ (1/2) m v²
|
|
380
|
+
potential : float
|
|
381
|
+
Coherence potential U from TNFR Hamiltonian
|
|
382
|
+
total : float
|
|
383
|
+
Total energy H = K + U
|
|
384
|
+
|
|
385
|
+
Notes
|
|
386
|
+
-----
|
|
387
|
+
Unlike classical n-body (assumes U = -Gm₁m₂/r), the potential
|
|
388
|
+
here emerges from TNFR coherence matrix and coupling topology.
|
|
389
|
+
"""
|
|
390
|
+
# Kinetic energy (same as classical)
|
|
391
|
+
v_squared = np.sum(self.velocities**2, axis=1)
|
|
392
|
+
kinetic = 0.5 * np.sum(self.masses * v_squared)
|
|
393
|
+
|
|
394
|
+
# Coherence potential from TNFR Hamiltonian
|
|
395
|
+
# This is the key difference: NO assumption about gravity
|
|
396
|
+
potential = compute_tnfr_coherence_potential(
|
|
397
|
+
self.graph, self.positions, self.hbar_str
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
total = kinetic + potential
|
|
401
|
+
|
|
402
|
+
return kinetic, potential, total
|
|
403
|
+
|
|
404
|
+
def compute_momentum(self) -> NDArray[np.floating]:
|
|
405
|
+
"""Compute total linear momentum.
|
|
406
|
+
|
|
407
|
+
Returns
|
|
408
|
+
-------
|
|
409
|
+
momentum : ndarray, shape (3,)
|
|
410
|
+
Total momentum P = Σ m v
|
|
411
|
+
"""
|
|
412
|
+
momentum = np.sum(self.masses[:, np.newaxis] * self.velocities, axis=0)
|
|
413
|
+
return momentum
|
|
414
|
+
|
|
415
|
+
def compute_angular_momentum(self) -> NDArray[np.floating]:
|
|
416
|
+
"""Compute total angular momentum.
|
|
417
|
+
|
|
418
|
+
Returns
|
|
419
|
+
-------
|
|
420
|
+
angular_momentum : ndarray, shape (3,)
|
|
421
|
+
Total L = Σ r × (m v)
|
|
422
|
+
"""
|
|
423
|
+
L = np.zeros(3)
|
|
424
|
+
for i in range(self.n_bodies):
|
|
425
|
+
L += self.masses[i] * np.cross(self.positions[i], self.velocities[i])
|
|
426
|
+
return L
|
|
427
|
+
|
|
428
|
+
def step(self, dt: float) -> None:
|
|
429
|
+
"""Advance system by one time step using TNFR dynamics.
|
|
430
|
+
|
|
431
|
+
This implements the nodal equation: ∂EPI/∂t = νf · ΔNFR
|
|
432
|
+
where ΔNFR is computed from Hamiltonian commutator.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
dt : float
|
|
437
|
+
Time step (structural time units)
|
|
438
|
+
|
|
439
|
+
Notes
|
|
440
|
+
-----
|
|
441
|
+
Uses velocity Verlet-like integration for position/velocity,
|
|
442
|
+
but accelerations come from TNFR ΔNFR via coherence gradients.
|
|
443
|
+
"""
|
|
444
|
+
# Update graph with current state
|
|
445
|
+
self._update_graph()
|
|
446
|
+
|
|
447
|
+
# Compute accelerations from TNFR coherence-based forces
|
|
448
|
+
# This is the key: forces emerge from coherence gradient, not gravity
|
|
449
|
+
accel = self._compute_tnfr_accelerations()
|
|
450
|
+
|
|
451
|
+
# Velocity Verlet integration
|
|
452
|
+
# v(t+dt/2) = v(t) + a(t) * dt/2
|
|
453
|
+
v_half = self.velocities + 0.5 * accel * dt
|
|
454
|
+
|
|
455
|
+
# r(t+dt) = r(t) + v(t+dt/2) * dt
|
|
456
|
+
self.positions += v_half * dt
|
|
457
|
+
|
|
458
|
+
# Update graph with new positions
|
|
459
|
+
self._update_graph()
|
|
460
|
+
|
|
461
|
+
# Recompute accelerations at new positions
|
|
462
|
+
accel_new = self._compute_tnfr_accelerations()
|
|
463
|
+
|
|
464
|
+
# v(t+dt) = v(t+dt/2) + a(t+dt) * dt/2
|
|
465
|
+
self.velocities = v_half + 0.5 * accel_new * dt
|
|
466
|
+
|
|
467
|
+
# Update phases based on ΔNFR
|
|
468
|
+
# Compute scalar ΔNFR for phase evolution
|
|
469
|
+
node_ids = [f"body_{i}" for i in range(self.n_bodies)]
|
|
470
|
+
dnfr_values = compute_tnfr_delta_nfr(
|
|
471
|
+
self.graph, node_ids, self.hbar_str
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
# Phase evolution: dθ/dt ~ ΔNFR
|
|
475
|
+
self.phases += dnfr_values * dt
|
|
476
|
+
self.phases = np.mod(self.phases, 2 * np.pi) # Keep in [0, 2π]
|
|
477
|
+
|
|
478
|
+
# Update time
|
|
479
|
+
self.time += dt
|
|
480
|
+
|
|
481
|
+
def _compute_tnfr_accelerations(self) -> NDArray[np.floating]:
|
|
482
|
+
"""Compute accelerations from TNFR coherence-based forces.
|
|
483
|
+
|
|
484
|
+
This is where TNFR physics determines motion:
|
|
485
|
+
- Forces emerge from coherence gradient (NOT gravity!)
|
|
486
|
+
- Phase differences create attraction/repulsion
|
|
487
|
+
- Coupling strength determines force magnitude
|
|
488
|
+
|
|
489
|
+
Returns
|
|
490
|
+
-------
|
|
491
|
+
accelerations : ndarray, shape (N, 3)
|
|
492
|
+
Acceleration vectors for each body
|
|
493
|
+
|
|
494
|
+
Notes
|
|
495
|
+
-----
|
|
496
|
+
The key TNFR insight: Forces emerge from maximizing coherence.
|
|
497
|
+
|
|
498
|
+
Coherence between nodes i and j depends on:
|
|
499
|
+
1. Phase difference: cos(θᵢ - θⱼ) (in-phase → attractive)
|
|
500
|
+
2. Coupling strength: J₀ (from network edges)
|
|
501
|
+
3. Distance dependence: Coherence decreases with separation
|
|
502
|
+
|
|
503
|
+
This gives rise to attraction/repulsion WITHOUT assuming gravity!
|
|
504
|
+
"""
|
|
505
|
+
accelerations = np.zeros((self.n_bodies, 3))
|
|
506
|
+
|
|
507
|
+
# For each pair of bodies
|
|
508
|
+
for i in range(self.n_bodies):
|
|
509
|
+
for j in range(self.n_bodies):
|
|
510
|
+
if i == j:
|
|
511
|
+
continue
|
|
512
|
+
|
|
513
|
+
# Position difference
|
|
514
|
+
r_ij = self.positions[j] - self.positions[i]
|
|
515
|
+
dist = np.linalg.norm(r_ij)
|
|
516
|
+
|
|
517
|
+
if dist < 1e-10:
|
|
518
|
+
continue # Avoid singularity
|
|
519
|
+
|
|
520
|
+
# Unit vector from i to j
|
|
521
|
+
r_hat = r_ij / dist
|
|
522
|
+
|
|
523
|
+
# Phase difference (key TNFR element!)
|
|
524
|
+
phase_diff = self.phases[j] - self.phases[i]
|
|
525
|
+
|
|
526
|
+
# Coherence factor: positive when in-phase, negative when anti-phase
|
|
527
|
+
# This creates attraction for synchronized nodes
|
|
528
|
+
coherence_factor = np.cos(phase_diff)
|
|
529
|
+
|
|
530
|
+
# Distance-dependent coupling (coherence decays with distance)
|
|
531
|
+
# This emerges from spatial structure of coherence matrix
|
|
532
|
+
# Use exponential decay or power law
|
|
533
|
+
distance_factor = 1.0 / (dist**2 + 0.1) # Softened power law
|
|
534
|
+
|
|
535
|
+
# Frequency coupling: Both nodes contribute
|
|
536
|
+
nu_i = 1.0 / self.masses[i]
|
|
537
|
+
nu_j = 1.0 / self.masses[j]
|
|
538
|
+
freq_factor = np.sqrt(nu_i * nu_j)
|
|
539
|
+
|
|
540
|
+
# Total TNFR force magnitude
|
|
541
|
+
force_mag = (
|
|
542
|
+
self.coupling_strength *
|
|
543
|
+
self.coherence_strength * # Negative = attractive well
|
|
544
|
+
coherence_factor *
|
|
545
|
+
distance_factor *
|
|
546
|
+
freq_factor
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
# Force direction
|
|
550
|
+
force_vec = force_mag * r_hat
|
|
551
|
+
|
|
552
|
+
# Acceleration: a = F/m = F * νf
|
|
553
|
+
accelerations[i] += force_vec * nu_i
|
|
554
|
+
|
|
555
|
+
return accelerations
|
|
556
|
+
|
|
557
|
+
def _update_graph(self) -> None:
|
|
558
|
+
"""Update graph representation with current state."""
|
|
559
|
+
for i in range(self.n_bodies):
|
|
560
|
+
node_id = f"body_{i}"
|
|
561
|
+
|
|
562
|
+
# Update EPI
|
|
563
|
+
epi_state = {
|
|
564
|
+
"position": self.positions[i].copy(),
|
|
565
|
+
"velocity": self.velocities[i].copy(),
|
|
566
|
+
}
|
|
567
|
+
self.graph.nodes[node_id]["epi"] = epi_state
|
|
568
|
+
|
|
569
|
+
# Update phase
|
|
570
|
+
self.graph.nodes[node_id]["theta"] = float(self.phases[i])
|
|
571
|
+
|
|
572
|
+
def evolve(
|
|
573
|
+
self,
|
|
574
|
+
t_final: float,
|
|
575
|
+
dt: float,
|
|
576
|
+
store_interval: int = 1,
|
|
577
|
+
) -> Dict[str, Any]:
|
|
578
|
+
"""Evolve system using pure TNFR dynamics.
|
|
579
|
+
|
|
580
|
+
Parameters
|
|
581
|
+
----------
|
|
582
|
+
t_final : float
|
|
583
|
+
Final time
|
|
584
|
+
dt : float
|
|
585
|
+
Time step
|
|
586
|
+
store_interval : int, default=1
|
|
587
|
+
Store state every N steps
|
|
588
|
+
|
|
589
|
+
Returns
|
|
590
|
+
-------
|
|
591
|
+
history : dict
|
|
592
|
+
Contains: time, positions, velocities, phases,
|
|
593
|
+
energy, kinetic, potential, momentum, angular_momentum
|
|
594
|
+
|
|
595
|
+
Notes
|
|
596
|
+
-----
|
|
597
|
+
Evolution follows nodal equation with ΔNFR from Hamiltonian.
|
|
598
|
+
NO classical gravitational assumptions.
|
|
599
|
+
"""
|
|
600
|
+
n_steps = int((t_final - self.time) / dt)
|
|
601
|
+
|
|
602
|
+
if n_steps < 1:
|
|
603
|
+
raise ValueError(f"t_final {t_final} <= current time {self.time}")
|
|
604
|
+
|
|
605
|
+
# Pre-allocate storage
|
|
606
|
+
n_stored = (n_steps // store_interval) + 1
|
|
607
|
+
times = np.zeros(n_stored)
|
|
608
|
+
positions_hist = np.zeros((n_stored, self.n_bodies, 3))
|
|
609
|
+
velocities_hist = np.zeros((n_stored, self.n_bodies, 3))
|
|
610
|
+
phases_hist = np.zeros((n_stored, self.n_bodies))
|
|
611
|
+
energies = np.zeros(n_stored)
|
|
612
|
+
kinetic_energies = np.zeros(n_stored)
|
|
613
|
+
potential_energies = np.zeros(n_stored)
|
|
614
|
+
momenta = np.zeros((n_stored, 3))
|
|
615
|
+
angular_momenta = np.zeros((n_stored, 3))
|
|
616
|
+
|
|
617
|
+
# Store initial state
|
|
618
|
+
store_idx = 0
|
|
619
|
+
times[store_idx] = self.time
|
|
620
|
+
positions_hist[store_idx] = self.positions.copy()
|
|
621
|
+
velocities_hist[store_idx] = self.velocities.copy()
|
|
622
|
+
phases_hist[store_idx] = self.phases.copy()
|
|
623
|
+
K, U, E = self.compute_energy()
|
|
624
|
+
kinetic_energies[store_idx] = K
|
|
625
|
+
potential_energies[store_idx] = U
|
|
626
|
+
energies[store_idx] = E
|
|
627
|
+
momenta[store_idx] = self.compute_momentum()
|
|
628
|
+
angular_momenta[store_idx] = self.compute_angular_momentum()
|
|
629
|
+
store_idx += 1
|
|
630
|
+
|
|
631
|
+
# Evolution loop
|
|
632
|
+
for step in range(n_steps):
|
|
633
|
+
self.step(dt)
|
|
634
|
+
|
|
635
|
+
# Store state if needed
|
|
636
|
+
if (step + 1) % store_interval == 0 and store_idx < n_stored:
|
|
637
|
+
times[store_idx] = self.time
|
|
638
|
+
positions_hist[store_idx] = self.positions.copy()
|
|
639
|
+
velocities_hist[store_idx] = self.velocities.copy()
|
|
640
|
+
phases_hist[store_idx] = self.phases.copy()
|
|
641
|
+
K, U, E = self.compute_energy()
|
|
642
|
+
kinetic_energies[store_idx] = K
|
|
643
|
+
potential_energies[store_idx] = U
|
|
644
|
+
energies[store_idx] = E
|
|
645
|
+
momenta[store_idx] = self.compute_momentum()
|
|
646
|
+
angular_momenta[store_idx] = self.compute_angular_momentum()
|
|
647
|
+
store_idx += 1
|
|
648
|
+
|
|
649
|
+
# Compute energy drift
|
|
650
|
+
energy_drift = abs(energies[-1] - energies[0]) / abs(energies[0])
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
"time": times[:store_idx],
|
|
654
|
+
"positions": positions_hist[:store_idx],
|
|
655
|
+
"velocities": velocities_hist[:store_idx],
|
|
656
|
+
"phases": phases_hist[:store_idx],
|
|
657
|
+
"energy": energies[:store_idx],
|
|
658
|
+
"kinetic": kinetic_energies[:store_idx],
|
|
659
|
+
"potential": potential_energies[:store_idx],
|
|
660
|
+
"momentum": momenta[:store_idx],
|
|
661
|
+
"angular_momentum": angular_momenta[:store_idx],
|
|
662
|
+
"energy_drift": energy_drift,
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
def plot_trajectories(
|
|
666
|
+
self,
|
|
667
|
+
history: Dict[str, Any],
|
|
668
|
+
show_energy: bool = True,
|
|
669
|
+
show_phases: bool = True,
|
|
670
|
+
) -> Figure:
|
|
671
|
+
"""Plot trajectories, energy, and phase evolution.
|
|
672
|
+
|
|
673
|
+
Parameters
|
|
674
|
+
----------
|
|
675
|
+
history : dict
|
|
676
|
+
Result from evolve()
|
|
677
|
+
show_energy : bool, default=True
|
|
678
|
+
Show energy conservation plot
|
|
679
|
+
show_phases : bool, default=True
|
|
680
|
+
Show phase evolution plot
|
|
681
|
+
|
|
682
|
+
Returns
|
|
683
|
+
-------
|
|
684
|
+
fig : matplotlib Figure
|
|
685
|
+
|
|
686
|
+
Raises
|
|
687
|
+
------
|
|
688
|
+
ImportError
|
|
689
|
+
If matplotlib not available
|
|
690
|
+
"""
|
|
691
|
+
try:
|
|
692
|
+
import matplotlib.pyplot as plt
|
|
693
|
+
from mpl_toolkits.mplot3d import Axes3D
|
|
694
|
+
except ImportError as exc:
|
|
695
|
+
raise ImportError(
|
|
696
|
+
"matplotlib required for plotting. "
|
|
697
|
+
"Install with: pip install 'tnfr[viz-basic]'"
|
|
698
|
+
) from exc
|
|
699
|
+
|
|
700
|
+
n_plots = 1 + int(show_energy) + int(show_phases)
|
|
701
|
+
fig = plt.figure(figsize=(6 * n_plots, 5))
|
|
702
|
+
|
|
703
|
+
plot_idx = 1
|
|
704
|
+
|
|
705
|
+
# 3D trajectories
|
|
706
|
+
ax_3d = fig.add_subplot(1, n_plots, plot_idx, projection="3d")
|
|
707
|
+
plot_idx += 1
|
|
708
|
+
|
|
709
|
+
positions = history["positions"]
|
|
710
|
+
colors = plt.cm.rainbow(np.linspace(0, 1, self.n_bodies))
|
|
711
|
+
|
|
712
|
+
for i in range(self.n_bodies):
|
|
713
|
+
traj = positions[:, i, :]
|
|
714
|
+
ax_3d.plot(
|
|
715
|
+
traj[:, 0],
|
|
716
|
+
traj[:, 1],
|
|
717
|
+
traj[:, 2],
|
|
718
|
+
color=colors[i],
|
|
719
|
+
label=f"Body {i+1} (m={self.masses[i]:.2f})",
|
|
720
|
+
alpha=0.7,
|
|
721
|
+
)
|
|
722
|
+
ax_3d.scatter(
|
|
723
|
+
traj[0, 0], traj[0, 1], traj[0, 2],
|
|
724
|
+
color=colors[i], s=100, marker="o"
|
|
725
|
+
)
|
|
726
|
+
ax_3d.scatter(
|
|
727
|
+
traj[-1, 0], traj[-1, 1], traj[-1, 2],
|
|
728
|
+
color=colors[i], s=50, marker="x"
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
ax_3d.set_xlabel("X")
|
|
732
|
+
ax_3d.set_ylabel("Y")
|
|
733
|
+
ax_3d.set_zlabel("Z")
|
|
734
|
+
ax_3d.set_title("TNFR N-Body Trajectories (No Gravitational Assumption)")
|
|
735
|
+
ax_3d.legend()
|
|
736
|
+
|
|
737
|
+
# Energy conservation
|
|
738
|
+
if show_energy:
|
|
739
|
+
ax_energy = fig.add_subplot(1, n_plots, plot_idx)
|
|
740
|
+
plot_idx += 1
|
|
741
|
+
|
|
742
|
+
time = history["time"]
|
|
743
|
+
E = history["energy"]
|
|
744
|
+
E0 = E[0]
|
|
745
|
+
|
|
746
|
+
ax_energy.plot(
|
|
747
|
+
time,
|
|
748
|
+
(E - E0) / abs(E0) * 100,
|
|
749
|
+
label="Energy drift (%)",
|
|
750
|
+
color="red",
|
|
751
|
+
linewidth=2,
|
|
752
|
+
)
|
|
753
|
+
ax_energy.axhline(0, color="black", linestyle="--", alpha=0.3)
|
|
754
|
+
ax_energy.set_xlabel("Structural Time")
|
|
755
|
+
ax_energy.set_ylabel("ΔE/E₀ (%)")
|
|
756
|
+
ax_energy.set_title("Energy Conservation (TNFR Hamiltonian)")
|
|
757
|
+
ax_energy.legend()
|
|
758
|
+
ax_energy.grid(True, alpha=0.3)
|
|
759
|
+
|
|
760
|
+
# Phase evolution
|
|
761
|
+
if show_phases:
|
|
762
|
+
ax_phases = fig.add_subplot(1, n_plots, plot_idx)
|
|
763
|
+
|
|
764
|
+
time = history["time"]
|
|
765
|
+
phases = history["phases"]
|
|
766
|
+
|
|
767
|
+
for i in range(self.n_bodies):
|
|
768
|
+
ax_phases.plot(
|
|
769
|
+
time,
|
|
770
|
+
phases[:, i],
|
|
771
|
+
color=colors[i],
|
|
772
|
+
label=f"Body {i+1}",
|
|
773
|
+
linewidth=2,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
ax_phases.set_xlabel("Structural Time")
|
|
777
|
+
ax_phases.set_ylabel("Phase θ (rad)")
|
|
778
|
+
ax_phases.set_title("Phase Evolution (TNFR Synchronization)")
|
|
779
|
+
ax_phases.legend()
|
|
780
|
+
ax_phases.grid(True, alpha=0.3)
|
|
781
|
+
|
|
782
|
+
plt.tight_layout()
|
|
783
|
+
return fig
|