File size: 7,066 Bytes
faf5ff0
 
 
 
 
 
 
 
 
 
 
4e95a36
22c5fe9
197e07d
 
22c5fe9
 
faf5ff0
f4a7c39
22c5fe9
faf5ff0
 
 
 
 
 
cb02dd8
faf5ff0
 
 
cb02dd8
22c5fe9
9d3fe8b
af62927
9d3fe8b
faf5ff0
 
 
 
 
197e07d
22c5fe9
faf5ff0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7ee5c66
faf5ff0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cb02dd8
0c5d221
faf5ff0
 
 
 
 
 
 
 
 
 
 
 
 
af62927
cb02dd8
faf5ff0
 
 
 
 
 
7760b04
faf5ff0
 
4162e1c
7760b04
faf5ff0
cb02dd8
faf5ff0
af62927
faf5ff0
af62927
 
faf5ff0
af62927
faf5ff0
 
 
c42c20c
faf5ff0
 
 
7760b04
 
4162e1c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# ==============================================================================
# Causal Convergence Simulator (Final Documented Version)
# ==============================================================================
# Author: Carlos R. Santos (in collaboration with a development partner)
#
# This Gradio application provides a real-time, interactive 3D simulation
# of the Causal Convergence principle. An agent (sphere) autonomously
# navigates a 3D space by learning from its immediate past ("causal echo")
# to determine the most logical next step towards a new random target.
# ==============================================================================

import gradio as gr
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math
from scipy.special import comb
import json
import time

# --- Core Mathematical Functions ---

def bernstein_poly(i, n, t):
    """ The Bernstein polynomial, which is the basis for Bézier curves. """
    return comb(n, i) * (t**(i)) * ((1 - t)**(n - i))

def bezier_curve_3d(points, n_times=20):
    """ Generates a 3D Bézier curve from a list of control points. """
    n_points = len(points)
    points_q = [np.array([quantize(p[0]), quantize(p[1]), quantize(p[2])]) for p in points]
    x_points, y_points, z_points = np.array([p[0] for p in points_q]), np.array([p[1] for p in points_q]), np.array([p[2] for p in points_q])
    t = np.linspace(0.0, 1.0, n_times)
    polynomial_array = np.array([bernstein_poly(i, n_points - 1, t) for i in range(n_points)])
    x_vals, y_vals, z_vals = np.dot(x_points, polynomial_array), np.dot(y_points, polynomial_array), np.dot(z_points, polynomial_array)
    return x_vals, y_vals, z_vals

def learn_from_echo_3d(echo_points: list):
    """ Calculates the essence of motion (the inertia vector) from the echo. """
    if len(echo_points) < 2: return {"velocity_vector": np.array([0, 0, 0])}
    p1, p2 = np.array(echo_points[0]), np.array(echo_points[-1])
    return {"velocity_vector": p2 - p1}

def quantize(value, multiple=4):
    """ Rounds a value to the nearest specified multiple. """
    return multiple * round(value / multiple)

# --- Main Gradio Simulation Engine ---

def infinite_simulation_engine(camera_angle: int):
    """
    A generator function that runs an infinite simulation loop, yielding
    a new plot for the Gradio UI on each frame.
    """
    # 1. Initialize the simulation state
    start_point = np.array([quantize(v) for v in [0., 0., 0.]])
    inertia_vector = np.array([quantize(v) for v in [10., 10., 10.]])
    trail_history = [start_point.tolist()]

    fig = plt.figure(figsize=(8, 8)); ax = fig.add_subplot(111, projection='3d')
    background_color = '#0a0a0a'; fig.patch.set_facecolor(background_color); ax.set_facecolor(background_color)
    
    cycle_num = 0
    while True: # The infinite loop
        cycle_num += 1
        
        # 2. The previous target becomes the new starting point
        current_point = np.array(trail_history[-1])
        
        # 3. Generate a new random target, quantized to the grid
        random_target_raw = np.random.rand(3) * 50 - 25
        next_target = np.array([quantize(v) for v in random_target_raw])

        # 4. Calculate the trajectory for the current cycle
        control_point = current_point + inertia_vector
        curve_points = [current_point, control_point, next_target]
        x_cycle, y_cycle, z_cycle = bezier_curve_3d(curve_points)

        # 5. Update the continuous trail history
        new_trail_points = list(zip(x_cycle, y_cycle, z_cycle))
        trail_history.extend(new_trail_points)
        max_trail_length = 150
        trail_history = trail_history[-max_trail_length:]
        
        # 6. Learn the inertia from the end of this cycle for the *next* one
        echo_size = 10
        echo_points = trail_history[-echo_size:]
        inertia_vector = learn_from_echo_3d(echo_points)["velocity_vector"]
        
        trail_np = np.array(trail_history)
        
        # 7. Render and yield each frame of the current cycle
        for frame_idx in range(len(x_cycle)):
            ax.cla() # Clear the plot for the new frame
            ax.xaxis.pane.fill = False; ax.yaxis.pane.fill = False; ax.zaxis.pane.fill = False
            ax.grid(color='#222222', linestyle='--'); ax.view_init(elev=30., azim=camera_angle)
            ax.set_xlim(-30, 30); ax.set_ylim(-30, 30); ax.set_zlim(-30, 30)
            ax.set_xticklabels([]); ax.set_yticklabels([]); ax.set_zticklabels([])

            # Draw static elements
            ax.scatter(*current_point, s=150, c='lime', alpha=0.7)
            ax.scatter(*next_target, s=150, c='red', marker='X', alpha=0.9)

            # Draw the gradient trail
            trail_end_index = len(trail_history) - len(x_cycle) + frame_idx
            trail_start_index = max(0, trail_end_index - 12)
            current_trail_segment = trail_history[trail_start_index:trail_end_index+1]
            if len(current_trail_segment) > 1:
                for i in range(len(current_trail_segment) - 1):
                    p1, p2 = current_trail_segment[i], current_trail_segment[i+1]
                    alpha = 0.8 * (i / 12)
                    ax.plot([p1[0], p2[0]], [p1[1], p2[1]], [p1[2], p2[2]], color='#ff4500', linewidth=4, alpha=alpha)

            # Draw the agent sphere
            ax.plot([x_cycle[frame_idx]], [y_cycle[frame_idx]], [z_cycle[frame_idx]], 'o', color='#ff4500', markersize=8, markeredgecolor='white')
            
            # Draw info text
            info_text = f"Cycle: {cycle_num}\nTarget: {np.round(next_target)}"; ax.text2D(0.05, 0.95, info_text, transform=ax.transAxes, color='white')
            
            yield fig
            time.sleep(0.01) # Controls animation speed for UI responsiveness

    plt.close(fig)

# --- Gradio User Interface ---
with gr.Blocks(theme=gr.themes.Base(primary_hue="purple", secondary_hue="orange")) as demo:
    gr.Markdown("# ✨ Causal Convergence Simulator ✨"); gr.Markdown("### The Mathematics of the Next Step")
    with gr.Tabs():
        with gr.TabItem("🔬 The Simulation"):
            with gr.Row():
                with gr.Column(scale=1):
                    gr.Markdown("Control the camera perspective and start the simulation. The agent (sphere) will navigate autonomously, generating new random targets and leaving a fading trail of its inertia."); camera_angle_slider = gr.Slider(-180, 180, value=25, label="Camera Angle (Azimuth)"); start_btn = gr.Button("🚀 Start Simulation", variant="primary")
                with gr.Column(scale=2):
                    plot_output = gr.Plot(label="Real-Time Visualization")
        with gr.TabItem("📜 The Theory"):
            # Load the explanation from an external markdown file
            with open("explanation.md", "r", encoding="utf-8") as f:
                gr.Markdown(f.read())
    
    start_btn.click(fn=infinite_simulation_engine, inputs=[camera_angle_slider], outputs=[plot_output])

if __name__ == "__main__":
    demo.launch()