RFTSystems commited on
Commit
5b375de
·
verified ·
1 Parent(s): a1a657f

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +430 -0
app.py ADDED
@@ -0,0 +1,430 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import time
4
+ import torch
5
+ import matplotlib.pyplot as plt
6
+ import tempfile
7
+
8
+ # Conditional import of cupy (GPU optional; this app runs CPU-only by default)
9
+ try:
10
+ import cupy as cp
11
+ _has_cupy = True
12
+ except ImportError:
13
+ _has_cupy = False
14
+ print("CuPy not found. Running in CPU-only mode.")
15
+
16
+ # --- CUDA kernel source (kept for future GPU support; not used in CPU path) ---
17
+ cuda_source = r'''
18
+ extern "C" {
19
+ __global__ void fused_mom_update(
20
+ const int Ncells,
21
+ const int Nmode,
22
+ const float dt,
23
+ const float eps,
24
+ const float sigma_const,
25
+ const float theta_global,
26
+ const float k_shred_global,
27
+ const float * __restrict__ d_alpha,
28
+ const float * __restrict__ d_gamma,
29
+ const float * __restrict__ d_omega,
30
+ float * __restrict__ d_mroot,
31
+ float * __restrict__ d_A,
32
+ float * __restrict__ d_Q,
33
+ unsigned int * __restrict__ d_event_counts,
34
+ unsigned long long * __restrict__ d_event_buffer
35
+ ) {
36
+ int cell_idx = blockIdx.x * blockDim.x + threadIdx.x;
37
+ if (cell_idx >= Ncells) return;
38
+ int base = cell_idx * Nmode;
39
+ float m = d_mroot[cell_idx];
40
+ float Xi = 0.0f;
41
+ for (int n = 0; n < Nmode; ++n) {
42
+ float A = d_A[base + n];
43
+ float Q = d_Q[base + n];
44
+ float A_dot = d_alpha[n] * m - d_gamma[n] * A + sigma_const * Q;
45
+ float f_drive = sigma_const * m * d_omega[n] * A;
46
+ float Q_dot = f_drive - Q;
47
+ A += dt * A_dot;
48
+ Q += dt * Q_dot;
49
+ d_A[base + n] = A;
50
+ d_Q[base + n] = Q;
51
+ Xi += d_omega[n] * A;
52
+ }
53
+ float Xi_norm = Xi / (m + eps);
54
+ if (Xi_norm >= theta_global) {
55
+ float eta = 1.0f - expf(-k_shred_global * (Xi_norm - theta_global));
56
+ if (eta < 0.0f) eta = 0.0f;
57
+ if (eta > 1.0f) eta = 1.0f;
58
+ float diss = 0.01f * m * eta;
59
+ float m_post = (1.0f - eta) * m - diss;
60
+ if (m_post < 0.0f) m_post = 0.0f;
61
+ d_mroot[cell_idx] = m_post;
62
+ unsigned int idx = atomicAdd(d_event_counts, 1u);
63
+ // Event buffer not used in this example
64
+ } else {
65
+ d_mroot[cell_idx] = m;
66
+ }
67
+ }
68
+ } // extern "C"
69
+ '''
70
+
71
+ # --- CPU kernel ---
72
+ def fused_mom_update_cpu(
73
+ m_root_t, A_t, Q_t, alpha_t, gamma_t, omega_t,
74
+ dt, eps, sigma_const, theta_global, k_shred_global,
75
+ event_counts_t=None, event_buffer_t=None
76
+ ):
77
+ # Ensure float32
78
+ m_root_t = m_root_t.to(torch.float32)
79
+ A_t = A_t.to(torch.float32)
80
+ Q_t = Q_t.to(torch.float32)
81
+ alpha_t = alpha_t.to(torch.float32)
82
+ gamma_t = gamma_t.to(torch.float32)
83
+ omega_t = omega_t.to(torch.float32)
84
+
85
+ # Expand params
86
+ alpha_exp = alpha_t.unsqueeze(0) # (1, Nmode)
87
+ gamma_exp = gamma_t.unsqueeze(0)
88
+ omega_exp = omega_t.unsqueeze(0)
89
+ m_root_exp = m_root_t.unsqueeze(1) # (Ncells, 1)
90
+
91
+ # Dynamics
92
+ A_dot = alpha_exp * m_root_exp - gamma_exp * A_t + sigma_const * Q_t
93
+ f_drive = sigma_const * m_root_exp * omega_exp * A_t
94
+ Q_dot = f_drive - Q_t
95
+
96
+ # Euler update
97
+ A_t.add_(dt * A_dot)
98
+ Q_t.add_(dt * Q_dot)
99
+
100
+ # Shredding
101
+ Xi = (omega_exp * A_t).sum(dim=1) # (Ncells)
102
+ Xi_norm = Xi / (m_root_t + eps) # (Ncells)
103
+ shred_mask = Xi_norm >= theta_global # bool mask
104
+
105
+ if torch.any(shred_mask):
106
+ eta_values = torch.zeros_like(Xi_norm)
107
+ eta_calc = 1.0 - torch.exp(-k_shred_global * (Xi_norm[shred_mask] - theta_global))
108
+ eta_values[shred_mask] = torch.clamp(eta_calc, 0.0, 1.0)
109
+
110
+ diss = 0.01 * m_root_t * eta_values
111
+ m_post = (1.0 - eta_values) * m_root_t - diss
112
+ m_post = torch.clamp(m_post, min=0.0)
113
+
114
+ m_root_t[shred_mask] = m_post[shred_mask]
115
+
116
+ shred_count = int(torch.sum(shred_mask).item())
117
+ if event_counts_t is not None:
118
+ if isinstance(event_counts_t, torch.Tensor):
119
+ if event_counts_t.dtype not in (torch.int64, torch.int32):
120
+ event_counts_t = event_counts_t.to(torch.int64)
121
+ event_counts_t.add_(shred_count)
122
+ else:
123
+ event_counts_t += shred_count
124
+
125
+ return m_root_t, A_t, Q_t, event_counts_t
126
+
127
+ # --- Kernel wrapper (CPU-first) ---
128
+ class MOMKernel:
129
+ def __init__(self, cuda_source, kernel_name='fused_mom_update', block_dim=128):
130
+ # CPU path by default; GPU path omitted for simplicity
131
+ self.use_cuda = False
132
+ self.kernel = fused_mom_update_cpu
133
+ self.device = torch.device('cpu')
134
+
135
+ def __call__(
136
+ self, m_root_t, A_t, Q_t, alpha_t, gamma_t, omega_t,
137
+ dt, eps, sigma_const, theta_global, k_shred_global,
138
+ event_counts_t=None, event_buffer_t=None
139
+ ):
140
+ return self.kernel(
141
+ m_root_t, A_t, Q_t, alpha_t, gamma_t, omega_t,
142
+ dt, eps, sigma_const, theta_global, k_shred_global,
143
+ event_counts_t, event_buffer_t
144
+ )
145
+
146
+ # --- System loop with feedback and onset tracking ---
147
+ class MOMSystemLoop:
148
+ def __init__(self, mom_kernel, m_root_initial, A_modes_initial, Q_drive_initial,
149
+ alpha, gamma, omega,
150
+ dt=0.02, eps=1e-6, sigma=0.75,
151
+ theta=2.2, k_shred=1.2,
152
+ event_buffer_size=1024):
153
+ self.mom_kernel = mom_kernel
154
+ self.device = mom_kernel.device
155
+
156
+ # State
157
+ self.m_root = m_root_initial.to(self.device).clone().to(torch.float32)
158
+ self.A_modes = A_modes_initial.to(self.device).clone().to(torch.float32)
159
+ self.Q_drive = Q_drive_initial.to(self.device).clone().to(torch.float32)
160
+
161
+ # Params
162
+ self.alpha = alpha.to(self.device).to(torch.float32)
163
+ self.gamma = gamma.to(self.device).to(torch.float32)
164
+ self.omega = omega.to(self.device).to(torch.float32)
165
+
166
+ self.dt = dt; self.eps = eps; self.sigma = sigma
167
+ self.theta = theta; self.k_shred = k_shred
168
+
169
+ # Event tracking
170
+ self.event_counts = torch.zeros((), dtype=torch.int64, device=self.device)
171
+ self.event_buffer = torch.zeros(event_buffer_size, dtype=torch.int64, device=self.device)
172
+
173
+ # Histories
174
+ self.m_root_history = []
175
+ self.A_modes_history = []
176
+ self.event_counts_history = []
177
+
178
+ # Shredding onset (per-cell first time reaching near-zero mass)
179
+ self.shred_onset = np.full((self.m_root.shape[0],), -1, dtype=np.int32)
180
+
181
+ def feedback(self, m_root, A_modes, Q_drive):
182
+ decay = 0.995
183
+ noise_level = 1e-4
184
+ A_modes_new = A_modes * decay + noise_level * torch.randn_like(A_modes, device=self.device)
185
+ A_modes_new = torch.clamp(A_modes_new, min=0.0)
186
+ m_root_new = m_root * decay + noise_level * torch.randn_like(m_root, device=self.device)
187
+ m_root_new = torch.clamp(m_root_new, min=0.0)
188
+ return m_root_new, A_modes_new, Q_drive
189
+
190
+ def run(self, iterations):
191
+ for i in range(iterations):
192
+ self.event_counts.zero_()
193
+
194
+ self.mom_kernel(
195
+ self.m_root, self.A_modes, self.Q_drive,
196
+ self.alpha, self.gamma, self.omega,
197
+ self.dt, self.eps, self.sigma, self.theta, self.k_shred,
198
+ self.event_counts, self.event_buffer
199
+ )
200
+
201
+ # Record shredding onset if mass is effectively collapsed
202
+ m_np = self.m_root.detach().cpu().numpy()
203
+ collapsed_mask = m_np <= 1e-8 # near-zero threshold to mark onset
204
+ for idx, collapsed in enumerate(collapsed_mask):
205
+ if collapsed and self.shred_onset[idx] == -1:
206
+ self.shred_onset[idx] = i
207
+
208
+ # Feedback after kernel update
209
+ self.m_root, self.A_modes, self.Q_drive = self.feedback(self.m_root, self.A_modes, self.Q_drive)
210
+
211
+ # Histories
212
+ self.m_root_history.append(float(self.m_root.mean().item()))
213
+ self.A_modes_history.append(float(self.A_modes.mean().item()))
214
+ self.event_counts_history.append(int(self.event_counts.item()))
215
+
216
+ # --- Simulation wrapper ---
217
+ def run_rft_simulation(
218
+ Ncells: int,
219
+ Nmode: int,
220
+ iterations: int,
221
+ dt: float = 0.02,
222
+ eps: float = 1e-6,
223
+ sigma: float = 0.75,
224
+ theta: float = 2.2,
225
+ k_shred: float = 1.2,
226
+ seed: int = 42
227
+ ):
228
+ torch.manual_seed(seed)
229
+ np.random.seed(seed)
230
+
231
+ # Kernel and device
232
+ mom_kernel_instance = MOMKernel(cuda_source, kernel_name='fused_mom_update', block_dim=128)
233
+ device = mom_kernel_instance.device
234
+
235
+ # Parameters on device
236
+ alpha = torch.empty(Nmode, device=device, dtype=torch.float32).uniform_(0.02, 0.12)
237
+ gamma = torch.empty(Nmode, device=device, dtype=torch.float32).uniform_(0.01, 0.06)
238
+ omega = torch.linspace(1.0, 8.0, Nmode, device=device, dtype=torch.float32)
239
+
240
+ # Initial states
241
+ m_root_initial = torch.ones(Ncells, device=device, dtype=torch.float32)
242
+ A_modes_initial = torch.rand(Ncells, Nmode, device=device, dtype=torch.float32) * 0.01
243
+ Q_drive_initial = torch.zeros(Ncells, Nmode, device=device, dtype=torch.float32)
244
+
245
+ mom_system = MOMSystemLoop(
246
+ mom_kernel_instance, m_root_initial, A_modes_initial, Q_drive_initial,
247
+ alpha, gamma, omega,
248
+ dt=dt, eps=eps, sigma=sigma, theta=theta, k_shred=k_shred
249
+ )
250
+
251
+ start_time = time.time()
252
+ mom_system.run(iterations)
253
+ elapsed_time = max(time.time() - start_time, 1e-9)
254
+
255
+ # GFLOPS estimate
256
+ ops_per_cell_per_iter = 12 * Nmode + 13
257
+ flops_per_iteration = float(Ncells) * float(ops_per_cell_per_iter)
258
+ total_flops = flops_per_iteration * float(iterations)
259
+ gflops = total_flops / (elapsed_time * 1e9)
260
+
261
+ return {
262
+ 'final_m_root': mom_system.m_root.detach().cpu().numpy().astype(np.float32),
263
+ 'final_A_modes': mom_system.A_modes.detach().cpu().numpy().astype(np.float32),
264
+ 'final_Q_drive': mom_system.Q_drive.detach().cpu().numpy().astype(np.float32),
265
+ 'm_root_history': np.array(mom_system.m_root_history, dtype=np.float32),
266
+ 'A_modes_history': np.array(mom_system.A_modes_history, dtype=np.float32),
267
+ 'event_counts_history': np.array(mom_system.event_counts_history, dtype=np.int64),
268
+ 'shred_onset': mom_system.shred_onset, # per-cell first-onset iteration or -1
269
+ 'elapsed_time_seconds': float(elapsed_time),
270
+ 'gflops': float(gflops),
271
+ }
272
+
273
+ # --- Gradio callback ---
274
+ def rft_simulation_interface(
275
+ Ncells: int,
276
+ Nmode: int,
277
+ iterations: int,
278
+ dt: float,
279
+ eps: float,
280
+ sigma: float,
281
+ theta: float,
282
+ k_shred: float
283
+ ):
284
+ try:
285
+ results = run_rft_simulation(
286
+ Ncells=Ncells,
287
+ Nmode=Nmode,
288
+ iterations=iterations,
289
+ dt=dt,
290
+ eps=eps,
291
+ sigma=sigma,
292
+ theta=theta,
293
+ k_shred=k_shred
294
+ )
295
+
296
+ # Create plots: 4 subplots including raster of shredding onset
297
+ fig = plt.figure(figsize=(10, 14))
298
+
299
+ # Plot 1: Mean m_root
300
+ ax1 = fig.add_subplot(4, 1, 1)
301
+ ax1.plot(results['m_root_history'], label='Mean m_root')
302
+ ax1.set_title('Mean m_root Over Iterations')
303
+ ax1.set_xlabel('Iteration')
304
+ ax1.set_ylabel('Mean m_root')
305
+ ax1.grid(True)
306
+ ax1.legend()
307
+
308
+ # Plot 2: Mean A_modes
309
+ ax2 = fig.add_subplot(4, 1, 2)
310
+ ax2.plot(results['A_modes_history'], label='Mean A_modes', color='orange')
311
+ ax2.set_title('Mean A_modes Over Iterations')
312
+ ax2.set_xlabel('Iteration')
313
+ ax2.set_ylabel('Mean A_modes')
314
+ ax2.grid(True)
315
+ ax2.legend()
316
+
317
+ # Plot 3: Cumulative Shredding Events
318
+ ax3 = fig.add_subplot(4, 1, 3)
319
+ cumulative_events = np.cumsum(results['event_counts_history'])
320
+ ax3.plot(cumulative_events, label='Cumulative Shredding Events', color='red')
321
+ ax3.set_title('Cumulative Shredding Events')
322
+ ax3.set_xlabel('Iteration')
323
+ ax3.set_ylabel('Cumulative Events')
324
+ ax3.grid(True)
325
+ ax3.legend()
326
+
327
+ # Plot 4: Raster of shredding onset per cell
328
+ ax4 = fig.add_subplot(4, 1, 4)
329
+ onset = results['shred_onset']
330
+ # Draw a vertical tick at the onset iteration for each cell that shredded
331
+ for idx, val in enumerate(onset):
332
+ if val >= 0:
333
+ ax4.vlines(val, idx, idx + 1, color='black', linewidth=0.8)
334
+ ax4.set_title('Shredding Onset per Cell')
335
+ ax4.set_xlabel('Iteration')
336
+ ax4.set_ylabel('Cell Index')
337
+ ax4.grid(True)
338
+
339
+ plt.tight_layout()
340
+ _, plot_path = tempfile.mkstemp(suffix=".png")
341
+ plt.savefig(plot_path)
342
+ plt.close(fig)
343
+
344
+ summary_output = (
345
+ f"Simulation completed in {results['elapsed_time_seconds']:.2f} seconds.\n\n"
346
+ f"Estimated GFLOPS: {results['gflops']:.2f}\n"
347
+ f"Final Mean m_root: {np.mean(results['final_m_root']):.6f}\n"
348
+ f"Final Mean A_modes: {np.mean(results['final_A_modes']):.6f}\n"
349
+ f"Total Events (last iteration): {results['event_counts_history'][-1] if len(results['event_counts_history']) > 0 else 0}\n\n"
350
+ f"-- Historical Data (first 5 values) --\n"
351
+ f"Mean m_root history: {results['m_root_history'][:5].tolist()}\n"
352
+ f"Mean A_modes history: {results['A_modes_history'][:5].tolist()}\n"
353
+ f"Event counts history: {results['event_counts_history'][:5].tolist()}"
354
+ )
355
+ except Exception as e:
356
+ summary_output = f"Error during RFT simulation: {e}"
357
+ plot_path = None
358
+
359
+ return summary_output, plot_path
360
+
361
+ # --- Explanatory markdown ---
362
+ what_is_this_markdown = '''
363
+ ### What is Render Frame Theory (RFT)?
364
+ Render Frame Theory (RFT) is a computational framework for simulating complex adaptive systems with emergent, non-linear dynamics. It models a system as a collection of cells, each with internal modes that evolve over time through coupled updates and event-driven transitions.
365
+
366
+ Key features:
367
+ - **Dynamic systems:** Evolves `m_root` (root mass), `A_modes` (mode amplitudes), and `Q_drive` (drive) over iterations.
368
+ - **Feedback loops:** Each iteration adjusts states based on prior values, enabling adaptation.
369
+ - **Emergent behavior:** A shredding mechanism triggers non-linear collapse when stress crosses a threshold.
370
+ - **Performance scaling:** Designed to scale with the number of cells and modes, enabling large explorations.
371
+
372
+ Why it matters:
373
+ - **Granularity:** Captures local interactions and cell-level transitions that averaged models miss.
374
+ - **Critical events:** Models sudden cascades like market crashes, neural avalanches, or material failure.
375
+ - **Versatility:** Applicable to finance, biology, engineering, and AI research.
376
+
377
+ The shredding onset plot shows when each cell first collapses, making cascades visible in time.
378
+ '''
379
+
380
+ # --- Gradio UI ---
381
+ with gr.Blocks(title="Render Frame Theory (RFT) Simulation Interface (CPU-ready)") as iface:
382
+ gr.Markdown(what_is_this_markdown)
383
+
384
+ with gr.Row():
385
+ with gr.Column():
386
+ gr.Markdown("### Simulation Parameters")
387
+ Ncells_slider = gr.Slider(minimum=16, maximum=512, step=16, value=64, label="⚡ Number of Cells (Ncells)")
388
+ Nmode_slider = gr.Slider(minimum=2, maximum=32, step=2, value=8, label="🔮 Number of Modes (Nmode)")
389
+ iterations_slider = gr.Slider(minimum=10, maximum=200, step=10, value=50, label="♾ Iterations")
390
+ dt_slider = gr.Slider(minimum=0.001, maximum=0.1, step=0.001, value=0.02, label="⌛ Time Step (dt)")
391
+ eps_slider = gr.Slider(
392
+ minimum=1e-7, maximum=1e-4, step=1e-7, value=1e-6,
393
+ label="🧿 Epsilon (eps)",
394
+ info="Small constant to stabilize Xi_norm division."
395
+ )
396
+ sigma_slider = gr.Slider(
397
+ minimum=0.1, maximum=1.0, step=0.05, value=0.75,
398
+ label="🌌 Sigma (coupling strength)",
399
+ info="Strength of Q–A interaction; higher values intensify dynamics."
400
+ )
401
+ theta_slider = gr.Slider(
402
+ minimum=0.1, maximum=5.0, step=0.1, value=2.2,
403
+ label="🔭 Theta (Shredding Threshold)",
404
+ info="Stress threshold (Xi_norm) above which shredding begins."
405
+ )
406
+ k_shred_slider = gr.Slider(
407
+ minimum=0.1, maximum=5.0, step=0.1, value=1.2,
408
+ label="🌀 K_shred (Shredding Rate)",
409
+ info="Intensity of shredding once triggered."
410
+ )
411
+
412
+ gr.Markdown("**Adjust parameters and click below to start the simulation.**")
413
+ run_button = gr.Button("Run Simulation")
414
+
415
+ with gr.Column():
416
+ gr.Markdown("### Simulation Results")
417
+ summary_output_textbox = gr.Textbox(label="Simulation Summary", lines=15)
418
+ plot_output_image = gr.Image(label="Simulation Plots", type="filepath")
419
+
420
+ run_button.click(
421
+ fn=rft_simulation_interface,
422
+ inputs=[
423
+ Ncells_slider, Nmode_slider, iterations_slider, dt_slider, eps_slider,
424
+ sigma_slider, theta_slider, k_shred_slider
425
+ ],
426
+ outputs=[summary_output_textbox, plot_output_image]
427
+ )
428
+
429
+ if __name__ == "__main__":
430
+ iface.launch(share=True)