FlameF0X commited on
Commit
ef3d490
·
verified ·
1 Parent(s): 9ef4115

Update src/App.jsx

Browse files
Files changed (1) hide show
  1. src/App.jsx +312 -220
src/App.jsx CHANGED
@@ -1,14 +1,14 @@
1
  import React, { useState, useEffect, useRef } from 'react';
2
- import {
3
- Activity,
4
- Settings,
5
- Play,
6
- Square,
7
- Trash2,
8
- Layers,
9
- Cpu,
10
- Zap,
11
- Plus,
12
  Info,
13
  Database,
14
  Move,
@@ -20,101 +20,99 @@ import {
20
  AlertTriangle
21
  } from 'lucide-react';
22
  import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
23
-
24
  // --- BLOCK DEFINITIONS ---
25
  const BLOCK_TYPES = {
26
- INPUT: {
27
- id: 'INPUT',
28
- label: 'Data In',
29
- category: 'IO',
30
- color: 'bg-emerald-500',
31
- outputs: 1,
32
  inputs: 0,
33
  config: { batchSize: 32, seqLen: 128, vocabSize: 5000 }
34
  },
35
- EMBED: {
36
- id: 'EMBED',
37
- label: 'Embedding',
38
- category: 'Core',
39
- color: 'bg-blue-500',
40
- outputs: 1,
41
  inputs: 1,
42
  config: { dim: 512 }
43
  },
44
- ATTN: {
45
- id: 'ATTN',
46
- label: 'Attention',
47
- category: 'Core',
48
- color: 'bg-indigo-600',
49
- outputs: 1,
50
  inputs: 1,
51
  config: { heads: 8, headDim: 64, dropout: 0.1 }
52
  },
53
- FFN: {
54
- id: 'FFN',
55
- label: 'Feed Forward',
56
- category: 'Core',
57
- color: 'bg-blue-600',
58
- outputs: 1,
59
  inputs: 1,
60
  config: { mult: 4, dropout: 0.1 }
61
  },
62
- NORM: {
63
- id: 'NORM',
64
- label: 'Layer Norm',
65
- category: 'Core',
66
- color: 'bg-slate-500',
67
- outputs: 1,
68
  inputs: 1,
69
  config: { eps: 1e-5 }
70
  },
71
- LSTM: {
72
- id: 'LSTM',
73
- label: 'LSTM',
74
- category: 'RNN',
75
- color: 'bg-amber-500',
76
- outputs: 1,
77
  inputs: 1,
78
  config: { hidden: 512, layers: 1 }
79
  },
80
- MAMBA: {
81
- id: 'MAMBA',
82
- label: 'Mamba (SSM)',
83
- category: 'Modern',
84
- color: 'bg-purple-600',
85
- outputs: 1,
86
  inputs: 1,
87
  config: { d_state: 16, d_conv: 4, expand: 2 }
88
  },
89
- RWKV: {
90
- id: 'RWKV',
91
- label: 'RWKV',
92
- category: 'Modern',
93
- color: 'bg-fuchsia-600',
94
- outputs: 1,
95
  inputs: 1,
96
  config: { mode: 'v5', head_size: 64 }
97
  },
98
- RESIDUAL: {
99
- id: 'RESIDUAL',
100
- label: 'Residual +',
101
- category: 'Utility',
102
- color: 'bg-cyan-500',
103
- outputs: 1,
104
  inputs: 2,
105
  config: {}
106
  },
107
- OUTPUT: {
108
- id: 'OUTPUT',
109
- label: 'Data Out',
110
- category: 'IO',
111
- color: 'bg-red-500',
112
- outputs: 0,
113
  inputs: 1,
114
  config: { loss: 'CrossEntropy' }
115
  }
116
  };
117
-
118
  const App = () => {
119
  // State for Blocks and Connections
120
  const [nodes, setNodes] = useState([
@@ -126,134 +124,254 @@ const App = () => {
126
  { id: 'c1', from: 'n1', to: 'n2' },
127
  { id: 'c2', from: 'n2', to: 'n3' }
128
  ]);
129
-
130
  // UI & Viewport State
131
  const [viewOffset, setViewOffset] = useState({ x: 0, y: 0 });
132
  const [zoom, setZoom] = useState(1);
133
  const [isPanning, setIsPanning] = useState(false);
134
  const [panStart, setPanStart] = useState({ x: 0, y: 0 });
135
-
136
  const [selectedNodeId, setSelectedNodeId] = useState(null);
137
  const [isTraining, setIsTraining] = useState(false);
138
  const [trainingData, setTrainingData] = useState([]);
139
  const [learningRate, setLearningRate] = useState(0.001);
140
  const [maxEpochs, setMaxEpochs] = useState(150);
141
  const [simAlert, setSimAlert] = useState(null);
142
-
143
  const [isDraggingNode, setIsDraggingNode] = useState(null);
144
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
145
  const [connectingFrom, setConnectingFrom] = useState(null);
146
-
147
  const canvasRef = useRef(null);
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- // --- ACCURATE SIMULATION ENGINE ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- const findPath = (startType, endType) => {
152
- const startNodes = nodes.filter(n => n.type === startType);
153
- const endNodes = nodes.filter(n => n.type === endType);
154
-
155
- for (const start of startNodes) {
156
- let visited = new Set();
157
- let queue = [[start.id, []]];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- while (queue.length > 0) {
160
- const [currId, path] = queue.shift();
161
- if (visited.has(currId)) continue;
162
- visited.add(currId);
 
 
 
 
 
 
 
 
 
 
163
 
164
- const currNode = nodes.find(n => n.id === currId);
165
- const currentPath = [...path, currNode];
 
 
166
 
167
- if (currNode.type === endType) return currentPath;
 
 
168
 
169
- const nextConnections = connections.filter(c => c.from === currId);
170
- for (const conn of nextConnections) {
171
- queue.push([conn.to, currentPath]);
172
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  }
174
- }
175
- return null;
176
- };
177
 
178
- const runTrainingStep = (epoch) => {
179
- // 1. Trace the graph to find the active computation path
180
- const activePath = findPath('INPUT', 'OUTPUT');
181
 
182
- if (!activePath) {
183
- setSimAlert("Broken Computation Path: Input cannot reach Output.");
184
- return null;
185
  }
186
 
187
- setSimAlert(null);
 
 
 
188
 
189
- // 2. Calculate Architectural Metrics
190
- let depth = activePath.length;
191
- let totalCapacity = 0;
192
- let vanishingGradientRisk = 1.0;
193
- let bottleneck = Infinity;
194
 
195
- activePath.forEach(node => {
196
- const type = node.type;
197
- const cfg = node.config;
198
 
199
- // Capacity based on hidden dimensions (Parameter scaling)
200
- if (cfg.dim) totalCapacity += cfg.dim;
201
- if (cfg.hidden) totalCapacity += cfg.hidden;
202
- if (cfg.heads) totalCapacity += (cfg.heads * cfg.headDim);
 
203
 
204
- // Bottleneck detection (Information Theory)
205
- if (cfg.dim) bottleneck = Math.min(bottleneck, cfg.dim);
 
 
206
 
207
- // Gradient stability factors
208
- if (type === 'LSTM') vanishingGradientRisk *= 0.95; // LSTM helps but still RNN
209
- if (type === 'ATTN' || type === 'MAMBA') vanishingGradientRisk *= 0.99; // Very stable
210
- if (type === 'NORM') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk * 1.5);
211
- if (type === 'RESIDUAL') vanishingGradientRisk = Math.min(1.0, vanishingGradientRisk * 1.8);
212
- });
 
 
 
 
 
 
 
213
 
214
- // 3. Realistic Training Physics
215
- // A. Learning Rate Explosion
216
- if (learningRate > 0.04 && Math.random() > 0.95) {
217
- setSimAlert("Gradient Explosion: Learning rate too high!");
218
- return { epoch, loss: (Math.random() * 10 + 5).toFixed(4), accuracy: "0.0000" };
219
  }
 
 
 
220
 
221
- // B. Vanishing Gradients
222
- // Probability of effective training decreases with depth unless stabilized
223
- const effectiveLearningPower = learningRate * Math.pow(vanishingGradientRisk, depth / 2);
224
 
225
- // C. Capacity vs Complexity
226
- // If bottleneck < 128, the model "underfits" (cannot reach low loss)
227
- const floorLoss = bottleneck < 128 ? 1.5 : 0.05;
 
 
228
 
229
- // D. The Loss Function (Log-Space Convergence)
230
- const prevLoss = trainingData.length > 0 ? parseFloat(trainingData[trainingData.length-1].loss) : 8.0;
231
-
232
- // Convergence speed depends on architecture efficiency
233
- const delta = (prevLoss - floorLoss) * effectiveLearningPower * 5;
234
- const noise = (Math.random() - 0.5) * (0.02 / (epoch + 1));
235
-
236
  const newLoss = Math.max(floorLoss, prevLoss - delta + noise);
 
 
237
  const newAcc = Math.min(0.99, 1 - (newLoss / 8) + (Math.random() * 0.01));
238
 
 
 
 
 
 
239
  return {
240
  epoch,
241
  loss: newLoss.toFixed(4),
242
  accuracy: newAcc.toFixed(4)
243
  };
244
  };
245
-
246
  useEffect(() => {
247
  let interval;
248
  if (isTraining) {
 
249
  interval = setInterval(() => {
250
  setTrainingData(prev => {
251
  const nextEpoch = prev.length;
252
- if (nextEpoch >= maxEpochs) {
253
- setIsTraining(false);
254
- return prev;
255
  }
256
- const step = runTrainingStep(nextEpoch);
 
257
  if (!step) {
258
  setIsTraining(false);
259
  return prev;
@@ -263,8 +381,7 @@ const App = () => {
263
  }, 100);
264
  }
265
  return () => clearInterval(interval);
266
- }, [isTraining, nodes, connections, learningRate, maxEpochs, trainingData]);
267
-
268
  // --- INTERACTION HANDLERS ---
269
  const handleWheel = (e) => {
270
  e.preventDefault();
@@ -272,7 +389,6 @@ const App = () => {
272
  const newZoom = Math.min(Math.max(zoom - e.deltaY * zoomSpeed, 0.2), 2);
273
  setZoom(newZoom);
274
  };
275
-
276
  const addNode = (type) => {
277
  const newNode = {
278
  id: `n${Date.now()}`,
@@ -283,17 +399,14 @@ const App = () => {
283
  };
284
  setNodes([...nodes, newNode]);
285
  };
286
-
287
  const deleteNode = (id) => {
288
  setNodes(nodes.filter(n => n.id !== id));
289
  setConnections(connections.filter(c => c.from !== id && c.to !== id));
290
  if (selectedNodeId === id) setSelectedNodeId(null);
291
  };
292
-
293
  const deleteConnection = (id) => {
294
  setConnections(connections.filter(c => c.id !== id));
295
  };
296
-
297
  const handleCanvasMouseDown = (e) => {
298
  if (e.target === canvasRef.current || e.target.tagName === 'svg' || e.target.id === 'grid-background') {
299
  setIsPanning(true);
@@ -301,7 +414,6 @@ const App = () => {
301
  setSelectedNodeId(null);
302
  }
303
  };
304
-
305
  const handleNodeMouseDown = (e, id) => {
306
  e.stopPropagation();
307
  if (connectingFrom) return;
@@ -310,7 +422,6 @@ const App = () => {
310
  setDragOffset({ x: e.clientX / zoom - node.x, y: e.clientY / zoom - node.y });
311
  setSelectedNodeId(id);
312
  };
313
-
314
  const handleMouseMove = (e) => {
315
  if (isDraggingNode) {
316
  setNodes(nodes.map(n => n.id === isDraggingNode ? { ...n, x: e.clientX / zoom - dragOffset.x, y: e.clientY / zoom - dragOffset.y } : n));
@@ -321,17 +432,14 @@ const App = () => {
321
  });
322
  }
323
  };
324
-
325
  const handleMouseUp = () => {
326
  setIsDraggingNode(null);
327
  setIsPanning(false);
328
  };
329
-
330
  const startConnection = (e, id) => {
331
  e.stopPropagation();
332
  setConnectingFrom(id);
333
  };
334
-
335
  const endConnection = (e, id) => {
336
  e.stopPropagation();
337
  if (connectingFrom && connectingFrom !== id) {
@@ -341,26 +449,26 @@ const App = () => {
341
  }
342
  setConnectingFrom(null);
343
  };
344
-
345
  const updateConfig = (key, val) => {
346
  setNodes(nodes.map(n => n.id === selectedNodeId ? { ...n, config: { ...n.config, [key]: val } } : n));
347
  };
348
-
349
  const resetView = () => {
350
  setViewOffset({ x: 0, y: 0 });
351
  setZoom(1);
352
  };
353
-
354
  const selectedNode = nodes.find(n => n.id === selectedNodeId);
355
-
356
  // Calculated properties for infinite grid
357
  const gridSize = 24 * zoom;
358
  const gridOffsetX = viewOffset.x % gridSize;
359
  const gridOffsetY = viewOffset.y % gridSize;
360
-
361
  const currentEpoch = trainingData.length;
362
  const trainingProgress = (currentEpoch / maxEpochs) * 100;
363
 
 
 
 
 
364
  return (
365
  <div className="flex flex-col h-screen w-screen bg-slate-950 text-slate-100 overflow-hidden font-sans">
366
  {/* Header */}
@@ -369,40 +477,39 @@ const App = () => {
369
  <div className="p-2 bg-indigo-500 rounded-lg shadow-[0_0_15px_rgba(99,102,241,0.4)]">
370
  <Layers size={20} className="text-white" />
371
  </div>
372
- <h1 className="text-lg font-bold tracking-tight">Arch-Sim <span className="text-slate-500 font-normal">v2.0 Accurate</span></h1>
373
  </div>
374
-
375
  <div className="flex items-center gap-4">
376
  <div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
377
  <span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider">LR</span>
378
- <input
379
- type="range"
380
- min="0.0001"
381
- max="0.05"
382
- step="0.0001"
383
- value={learningRate}
384
  onChange={(e) => setLearningRate(parseFloat(e.target.value))}
385
  className="w-20 accent-indigo-500"
386
  />
387
  <span className="text-[11px] font-mono text-indigo-300 w-10">{learningRate}</span>
388
  </div>
389
-
390
  <div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
391
  <Clock size={12} className="text-slate-400" />
392
  <span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider whitespace-nowrap">Max Epochs</span>
393
- <input
394
- type="range"
395
- min="50"
396
- max="1000"
397
- step="50"
398
- value={maxEpochs}
399
  onChange={(e) => setMaxEpochs(parseInt(e.target.value))}
400
  className="w-20 accent-indigo-500"
401
  />
402
  <span className="text-[11px] font-mono text-indigo-300 w-8">{maxEpochs}</span>
403
  </div>
404
-
405
- <button
406
  onClick={() => {
407
  if (isTraining) setIsTraining(false);
408
  else {
@@ -418,7 +525,6 @@ const App = () => {
418
  </button>
419
  </div>
420
  </header>
421
-
422
  <main className="flex-1 flex overflow-hidden">
423
  {/* Sidebar: Blocks Palette */}
424
  <aside className="w-64 border-r border-slate-800 bg-slate-900/30 p-4 flex flex-col gap-6 overflow-y-auto z-10">
@@ -444,9 +550,8 @@ const App = () => {
444
  </div>
445
  </div>
446
  </aside>
447
-
448
  {/* Canvas Area */}
449
- <div
450
  className="flex-1 relative bg-slate-950 overflow-hidden cursor-grab active:cursor-grabbing outline-none"
451
  onMouseDown={handleCanvasMouseDown}
452
  onMouseMove={handleMouseMove}
@@ -455,16 +560,15 @@ const App = () => {
455
  ref={canvasRef}
456
  >
457
  {/* Infinite Dot Grid Background */}
458
- <div
459
  id="grid-background"
460
  className="absolute inset-0 pointer-events-none"
461
- style={{
462
  backgroundImage: `radial-gradient(circle, #1e293b 1px, transparent 1px)`,
463
  backgroundSize: `${gridSize}px ${gridSize}px`,
464
  backgroundPosition: `${gridOffsetX}px ${gridOffsetY}px`
465
  }}
466
  />
467
-
468
  {/* Alert Message */}
469
  {simAlert && (
470
  <div className="absolute top-6 left-1/2 -translate-x-1/2 bg-red-500/90 text-white px-4 py-2 rounded-full flex items-center gap-2 text-sm font-bold shadow-2xl z-50 animate-bounce">
@@ -472,11 +576,10 @@ const App = () => {
472
  {simAlert}
473
  </div>
474
  )}
475
-
476
  {/* Viewport Transform Container */}
477
- <div
478
  className="absolute inset-0 pointer-events-none"
479
- style={{
480
  transform: `translate(${viewOffset.x}px, ${viewOffset.y}px) scale(${zoom})`,
481
  transformOrigin: '0 0'
482
  }}
@@ -492,13 +595,13 @@ const App = () => {
492
  const fromNode = nodes.find(n => n.id === conn.from);
493
  const toNode = nodes.find(n => n.id === conn.to);
494
  if (!fromNode || !toNode) return null;
495
-
496
  const x1 = fromNode.x + 160;
497
  const y1 = fromNode.y + 40;
498
  const x2 = toNode.x;
499
  const y2 = toNode.y + 40;
500
  const dx = (x2 - x1) / 2;
501
-
502
  return (
503
  <g key={conn.id} className="group cursor-pointer">
504
  <path
@@ -522,7 +625,6 @@ const App = () => {
522
  );
523
  })}
524
  </svg>
525
-
526
  {/* Nodes */}
527
  {nodes.map(node => {
528
  const type = BLOCK_TYPES[node.type];
@@ -546,12 +648,11 @@ const App = () => {
546
  </button>
547
  )}
548
  </div>
549
-
550
  <h4 className="text-xs font-bold uppercase tracking-wide truncate">{type.label}</h4>
551
  <div className="text-[10px] text-slate-500 mb-4">{node.id}</div>
552
-
553
  {type.inputs > 0 && (
554
- <div
555
  onMouseUp={(e) => endConnection(e, node.id)}
556
  className={`absolute -left-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 ${connectingFrom ? 'border-yellow-400 animate-pulse scale-125' : 'border-indigo-500'} flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30 transition-transform`}
557
  >
@@ -559,7 +660,7 @@ const App = () => {
559
  </div>
560
  )}
561
  {type.outputs > 0 && (
562
- <div
563
  onMouseDown={(e) => startConnection(e, node.id)}
564
  className="absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 border-indigo-500 flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30"
565
  >
@@ -570,24 +671,23 @@ const App = () => {
570
  );
571
  })}
572
  </div>
573
-
574
  {/* Navigation Controls Overlay */}
575
  <div className="absolute bottom-6 left-6 flex flex-col gap-2 z-20">
576
  <div className="flex gap-2">
577
- <button
578
  onClick={resetView}
579
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400 flex items-center gap-2"
580
  >
581
  <Maximize size={18} />
582
  <span className="text-[10px] font-bold uppercase tracking-widest">{Math.round(zoom * 100)}%</span>
583
  </button>
584
- <button
585
  onClick={() => setZoom(prev => Math.min(prev + 0.1, 2))}
586
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400"
587
  >
588
  <ZoomIn size={18} />
589
  </button>
590
- <button
591
  onClick={() => setZoom(prev => Math.max(prev - 0.1, 0.2))}
592
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400"
593
  >
@@ -596,7 +696,6 @@ const App = () => {
596
  </div>
597
  </div>
598
  </div>
599
-
600
  {/* Right Sidebar: Config & Monitoring */}
601
  <aside className="w-80 border-l border-slate-800 bg-slate-900/50 flex flex-col z-10">
602
  <div className="h-[55%] p-6 flex flex-col border-b border-slate-800 overflow-hidden">
@@ -608,15 +707,14 @@ const App = () => {
608
  Epoch {currentEpoch}/{maxEpochs}
609
  </div>
610
  </div>
611
-
612
  {/* Progress Bar */}
613
  <div className="w-full h-1.5 bg-slate-800 rounded-full mb-4 overflow-hidden">
614
- <div
615
- className="h-full bg-indigo-500 transition-all duration-300 shadow-[0_0_10px_rgba(99,102,241,0.5)]"
616
  style={{ width: `${trainingProgress}%` }}
617
  />
618
  </div>
619
-
620
  <div className="flex-1 bg-slate-900 rounded-xl p-2 border border-slate-800 overflow-hidden relative">
621
  {trainingData.length > 0 ? (
622
  <ResponsiveContainer width="100%" height="100%">
@@ -624,7 +722,7 @@ const App = () => {
624
  <CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
625
  <XAxis dataKey="epoch" hide />
626
  <YAxis hide domain={[0, 'auto']} />
627
- <Tooltip
628
  contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #1e293b', borderRadius: '8px' }}
629
  itemStyle={{ color: '#6366f1' }}
630
  />
@@ -633,11 +731,10 @@ const App = () => {
633
  </ResponsiveContainer>
634
  ) : (
635
  <div className="h-full flex flex-col items-center justify-center text-slate-600 italic text-xs text-center p-4">
636
- Simulation idle. Ensure INPUT is linked to OUTPUT and click Train.
637
  </div>
638
  )}
639
  </div>
640
-
641
  <div className="mt-4 grid grid-cols-2 gap-4">
642
  <div className="p-3 bg-slate-900/50 rounded-lg border border-slate-800 text-center">
643
  <div className="text-[10px] uppercase text-slate-500 font-bold mb-1">Loss</div>
@@ -653,12 +750,10 @@ const App = () => {
653
  </div>
654
  </div>
655
  </div>
656
-
657
  <div className="flex-1 p-6 overflow-y-auto">
658
  <div className="flex items-center gap-2 mb-6 text-slate-400 uppercase text-xs font-bold tracking-widest">
659
  <Settings size={14} /> {selectedNode ? 'Block Parameters' : 'Sim Intelligence'}
660
  </div>
661
-
662
  {selectedNode ? (
663
  <div className="space-y-6">
664
  <div className="flex items-center gap-3 p-3 bg-indigo-500/10 rounded-xl border border-indigo-500/20">
@@ -670,7 +765,6 @@ const App = () => {
670
  <div className="text-[10px] text-indigo-300 font-mono uppercase">{selectedNode.id}</div>
671
  </div>
672
  </div>
673
-
674
  <div className="space-y-4">
675
  {Object.entries(selectedNode.config).map(([key, val]) => (
676
  <div key={key} className="space-y-1.5">
@@ -692,25 +786,24 @@ const App = () => {
692
  ) : (
693
  <div className="space-y-4">
694
  <div className="p-4 bg-slate-800/30 rounded-xl border border-slate-700/50 text-xs text-slate-400 space-y-3 leading-relaxed">
695
- <p><strong className="text-slate-200 block mb-1">Graph Dependency:</strong> Training logic now uses a BFS trace. If a node isn't part of the direct path from Data In to Data Out, its parameters won't contribute to capacity.</p>
696
- <p><strong className="text-slate-200 block mb-1">Architecture Physics:</strong> Adding layers increases depth, which increases the risk of Vanishing Gradients unless you add <span className="text-cyan-400">Residual</span> or <span className="text-slate-300">Norm</span> blocks.</p>
697
- <p><strong className="text-slate-200 block mb-1">Convergence:</strong> The loss floor is determined by the "bottleneck" (the smallest hidden dimension in your path).</p>
698
  </div>
699
  </div>
700
  )}
701
  </div>
702
  </aside>
703
  </main>
704
-
705
  <footer className="h-10 border-t border-slate-800 bg-slate-900/80 px-4 flex items-center justify-between text-[10px] text-slate-500 uppercase tracking-widest font-semibold">
706
  <div className="flex gap-4">
707
  <span>Active Nodes: {nodes.length}</span>
708
- <span>Valid Path: {findPath('INPUT', 'OUTPUT') ? 'YES' : 'NO'}</span>
 
709
  <span>LR: {learningRate}</span>
710
  </div>
711
- <div>Scientific Arch-Sim Engine v2.0</div>
712
  </footer>
713
-
714
  <style>{`
715
  @keyframes dash {
716
  to { stroke-dashoffset: -12; }
@@ -729,5 +822,4 @@ const App = () => {
729
  </div>
730
  );
731
  };
732
-
733
  export default App;
 
1
  import React, { useState, useEffect, useRef } from 'react';
2
+ import {
3
+ Activity,
4
+ Settings,
5
+ Play,
6
+ Square,
7
+ Trash2,
8
+ Layers,
9
+ Cpu,
10
+ Zap,
11
+ Plus,
12
  Info,
13
  Database,
14
  Move,
 
20
  AlertTriangle
21
  } from 'lucide-react';
22
  import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
 
23
  // --- BLOCK DEFINITIONS ---
24
  const BLOCK_TYPES = {
25
+ INPUT: {
26
+ id: 'INPUT',
27
+ label: 'Data In',
28
+ category: 'IO',
29
+ color: 'bg-emerald-500',
30
+ outputs: 1,
31
  inputs: 0,
32
  config: { batchSize: 32, seqLen: 128, vocabSize: 5000 }
33
  },
34
+ EMBED: {
35
+ id: 'EMBED',
36
+ label: 'Embedding',
37
+ category: 'Core',
38
+ color: 'bg-blue-500',
39
+ outputs: 1,
40
  inputs: 1,
41
  config: { dim: 512 }
42
  },
43
+ ATTN: {
44
+ id: 'ATTN',
45
+ label: 'Attention',
46
+ category: 'Core',
47
+ color: 'bg-indigo-600',
48
+ outputs: 1,
49
  inputs: 1,
50
  config: { heads: 8, headDim: 64, dropout: 0.1 }
51
  },
52
+ FFN: {
53
+ id: 'FFN',
54
+ label: 'Feed Forward',
55
+ category: 'Core',
56
+ color: 'bg-blue-600',
57
+ outputs: 1,
58
  inputs: 1,
59
  config: { mult: 4, dropout: 0.1 }
60
  },
61
+ NORM: {
62
+ id: 'NORM',
63
+ label: 'Layer Norm',
64
+ category: 'Core',
65
+ color: 'bg-slate-500',
66
+ outputs: 1,
67
  inputs: 1,
68
  config: { eps: 1e-5 }
69
  },
70
+ LSTM: {
71
+ id: 'LSTM',
72
+ label: 'LSTM',
73
+ category: 'RNN',
74
+ color: 'bg-amber-500',
75
+ outputs: 1,
76
  inputs: 1,
77
  config: { hidden: 512, layers: 1 }
78
  },
79
+ MAMBA: {
80
+ id: 'MAMBA',
81
+ label: 'Mamba (SSM)',
82
+ category: 'Modern',
83
+ color: 'bg-purple-600',
84
+ outputs: 1,
85
  inputs: 1,
86
  config: { d_state: 16, d_conv: 4, expand: 2 }
87
  },
88
+ RWKV: {
89
+ id: 'RWKV',
90
+ label: 'RWKV',
91
+ category: 'Modern',
92
+ color: 'bg-fuchsia-600',
93
+ outputs: 1,
94
  inputs: 1,
95
  config: { mode: 'v5', head_size: 64 }
96
  },
97
+ RESIDUAL: {
98
+ id: 'RESIDUAL',
99
+ label: 'Residual +',
100
+ category: 'Utility',
101
+ color: 'bg-cyan-500',
102
+ outputs: 1,
103
  inputs: 2,
104
  config: {}
105
  },
106
+ OUTPUT: {
107
+ id: 'OUTPUT',
108
+ label: 'Data Out',
109
+ category: 'IO',
110
+ color: 'bg-red-500',
111
+ outputs: 0,
112
  inputs: 1,
113
  config: { loss: 'CrossEntropy' }
114
  }
115
  };
 
116
  const App = () => {
117
  // State for Blocks and Connections
118
  const [nodes, setNodes] = useState([
 
124
  { id: 'c1', from: 'n1', to: 'n2' },
125
  { id: 'c2', from: 'n2', to: 'n3' }
126
  ]);
127
+
128
  // UI & Viewport State
129
  const [viewOffset, setViewOffset] = useState({ x: 0, y: 0 });
130
  const [zoom, setZoom] = useState(1);
131
  const [isPanning, setIsPanning] = useState(false);
132
  const [panStart, setPanStart] = useState({ x: 0, y: 0 });
133
+
134
  const [selectedNodeId, setSelectedNodeId] = useState(null);
135
  const [isTraining, setIsTraining] = useState(false);
136
  const [trainingData, setTrainingData] = useState([]);
137
  const [learningRate, setLearningRate] = useState(0.001);
138
  const [maxEpochs, setMaxEpochs] = useState(150);
139
  const [simAlert, setSimAlert] = useState(null);
140
+
141
  const [isDraggingNode, setIsDraggingNode] = useState(null);
142
  const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
143
  const [connectingFrom, setConnectingFrom] = useState(null);
144
+
145
  const canvasRef = useRef(null);
146
+ // --- IMPROVED SIMULATION ENGINE ---
147
+ const analyzeGraph = () => {
148
+ // Build graph: predecessors and successors
149
+ const preds = {};
150
+ const succ = {};
151
+ connections.forEach(c => {
152
+ if (!succ[c.from]) succ[c.from] = [];
153
+ succ[c.from].push(c.to);
154
+ if (!preds[c.to]) preds[c.to] = [];
155
+ preds[c.to].push(c.from);
156
+ });
157
 
158
+ // Topological sort to detect cycles and order
159
+ const indegree = {};
160
+ nodes.forEach(n => indegree[n.id] = preds[n.id] ? preds[n.id].length : 0);
161
+ const queue = nodes.filter(n => indegree[n.id] === 0).map(n => n.id);
162
+ const order = [];
163
+ while (queue.length > 0) {
164
+ const id = queue.shift();
165
+ order.push(id);
166
+ if (succ[id]) {
167
+ succ[id].forEach(to => {
168
+ indegree[to]--;
169
+ if (indegree[to] === 0) queue.push(to);
170
+ });
171
+ }
172
+ }
173
+ if (order.length < nodes.length) {
174
+ return { error: "Cycle detected or disconnected graph." };
175
+ }
176
 
177
+ // Identify INPUT and OUTPUT
178
+ const inputNodes = nodes.filter(n => n.type === 'INPUT');
179
+ const outputNodes = nodes.filter(n => n.type === 'OUTPUT');
180
+ if (inputNodes.length !== 1 || outputNodes.length !== 1) {
181
+ return { error: "Exactly one INPUT and one OUTPUT required." };
182
+ }
183
+ const inputNode = inputNodes[0];
184
+ const outputNode = outputNodes[0];
185
+
186
+ // Extract global configs
187
+ const vocabSize = inputNode.config.vocabSize;
188
+ const batchSize = inputNode.config.batchSize;
189
+ const seqLen = inputNode.config.seqLen;
190
+
191
+ // Propagate dimensions and compute params, depths
192
+ const nodeDims = {}; // out_dim
193
+ const nodeParams = {};
194
+ const nodeDepths = {}; // max depth to this node
195
+ let totalParams = 0;
196
+ let minBottleneck = Infinity;
197
+ let vanishingGradientRisk = 1.0;
198
 
199
+ // Start with INPUT: dim=0 (tokens)
200
+ nodeDims[inputNode.id] = 0;
201
+ nodeParams[inputNode.id] = 0;
202
+ nodeDepths[inputNode.id] = 0;
203
+
204
+ for (const id of order) {
205
+ if (id === inputNode.id) continue;
206
+ const node = nodes.find(n => n.id === id);
207
+ const type = BLOCK_TYPES[node.type];
208
+ const inIds = preds[id] || [];
209
+ if (inIds.length !== type.inputs) {
210
+ return { error: `Invalid input count for ${node.label} (${id}). Expected ${type.inputs}, got ${inIds.length}.` };
211
+ }
212
+ const inDims = inIds.map(pre => nodeDims[pre]);
213
 
214
+ // Compute inDim (assume primary or check match)
215
+ let inDim = inDims[0];
216
+ let outDim;
217
+ let params = 0;
218
 
219
+ // Compute depth: max over predecessors +1
220
+ const depth = 1 + Math.max(...inIds.map(pre => nodeDepths[pre] || 0), 0);
221
+ nodeDepths[id] = depth;
222
 
223
+ switch (node.type) {
224
+ case 'RESIDUAL':
225
+ if (inDims[0] !== inDims[1]) {
226
+ return { error: `Dimension mismatch in Residual (${id}): ${inDims[0]} vs ${inDims[1]}.` };
227
+ }
228
+ outDim = inDim;
229
+ params = 0;
230
+ vanishingGradientRisk *= 0.7; // Residuals stabilize gradients significantly
231
+ break;
232
+ case 'EMBED':
233
+ if (inDim !== 0) return { error: `Embedding (${id}) expects token input (dim=0), got ${inDim}.` };
234
+ outDim = node.config.dim;
235
+ params = vocabSize * outDim; // Embedding matrix
236
+ break;
237
+ case 'ATTN':
238
+ if (node.config.heads * node.config.headDim !== inDim) {
239
+ // Allow projection if mismatch, but for realism, assume same or add proj params
240
+ params += inDim * (node.config.heads * node.config.headDim); // Proj in if mismatch
241
+ }
242
+ outDim = node.config.heads * node.config.headDim;
243
+ params += 4 * outDim * outDim; // QKV + Proj (approx, ignoring heads split)
244
+ vanishingGradientRisk *= 0.98; // Attention is stable
245
+ break;
246
+ case 'FFN':
247
+ const hidden = inDim * node.config.mult;
248
+ params = inDim * hidden + hidden * inDim; // Up + down
249
+ outDim = inDim;
250
+ vanishingGradientRisk *= 0.99;
251
+ break;
252
+ case 'NORM':
253
+ params = 2 * inDim; // Scale + bias
254
+ outDim = inDim;
255
+ vanishingGradientRisk *= 0.85; // Norm stabilizes
256
+ break;
257
+ case 'LSTM':
258
+ outDim = node.config.hidden;
259
+ params = node.config.layers * (4 * inDim * outDim + 4 * outDim); // Gates + biases
260
+ vanishingGradientRisk *= Math.pow(0.95, node.config.layers); // RNN vanishing
261
+ break;
262
+ case 'MAMBA':
263
+ outDim = inDim * node.config.expand;
264
+ params = inDim * outDim * 2 + node.config.d_state * outDim + node.config.d_conv * outDim; // Approx
265
+ vanishingGradientRisk *= 0.97; // SSM stable
266
+ break;
267
+ case 'RWKV':
268
+ outDim = inDim;
269
+ params = 5 * inDim * inDim; // Approx for time-mix, channel-mix
270
+ vanishingGradientRisk *= 0.96;
271
+ break;
272
+ case 'OUTPUT':
273
+ outDim = vocabSize;
274
+ params = inDim * vocabSize; // Linear head
275
+ if (inDim < 64) vanishingGradientRisk *= 1.2; // Small head unstable
276
+ break;
277
+ default:
278
+ return { error: `Unknown block type: ${node.type}.` };
279
  }
 
 
 
280
 
281
+ nodeDims[id] = outDim;
282
+ nodeParams[id] = params;
283
+ totalParams += params;
284
 
285
+ if (outDim > 0) minBottleneck = Math.min(minBottleneck, outDim);
 
 
286
  }
287
 
288
+ // Check OUTPUT dim
289
+ if (nodeDims[outputNode.id] !== vocabSize) {
290
+ return { error: `Output dimension mismatch: expected ${vocabSize}, got ${nodeDims[outputNode.id]}.` };
291
+ }
292
 
293
+ // Adjust vanishing risk with depth
294
+ const maxDepth = Math.max(...Object.values(nodeDepths));
295
+ vanishingGradientRisk = Math.pow(vanishingGradientRisk, maxDepth / 5); // Softer decay
 
 
296
 
297
+ // Fake dataset size (tokens)
298
+ const dataTokens = batchSize * seqLen * 10000; // Arbitrary large dataset
 
299
 
300
+ // Scaling laws (inspired by Chinchilla/Kaplan): loss ~ a / params^b + c
301
+ const a = 410; // Tuned for CE loss
302
+ const b = 0.28;
303
+ const c = 1.7;
304
+ let predictedFloorLoss = a / Math.pow(totalParams || 1, b) + c;
305
 
306
+ // Adjust for under/over-param
307
+ const paramDataRatio = totalParams / dataTokens;
308
+ if (paramDataRatio < 0.01) predictedFloorLoss *= 1.5; // Underfit
309
+ if (paramDataRatio > 10) predictedFloorLoss += 0.5; // Overfit plateau
310
 
311
+ // Bottleneck penalty
312
+ if (minBottleneck < 128) predictedFloorLoss += (128 - minBottleneck) / 100;
313
+
314
+ return {
315
+ totalParams,
316
+ maxDepth,
317
+ vanishingGradientRisk,
318
+ bottleneck: minBottleneck,
319
+ floorLoss: Math.max(0.1, predictedFloorLoss / 10), // Scale to reasonable CE range
320
+ batchSize,
321
+ error: null
322
+ };
323
+ };
324
 
325
+ const runTrainingStep = (epoch, graphMetrics, prevLoss) => {
326
+ if (graphMetrics.error) {
327
+ setSimAlert(graphMetrics.error);
328
+ return null;
 
329
  }
330
+ setSimAlert(null);
331
+
332
+ const { vanishingGradientRisk, floorLoss, batchSize, maxDepth } = graphMetrics;
333
 
334
+ // Effective learning: adjust by batch, stability, depth
335
+ const effectiveLR = learningRate * Math.sqrt(batchSize / 32) * vanishingGradientRisk;
 
336
 
337
+ // Gradient explosion check (more probable with high LR or unstable grad)
338
+ if (learningRate > 0.01 && Math.random() > vanishingGradientRisk) {
339
+ setSimAlert("Gradient Explosion: Reduce LR or add stabilization blocks!");
340
+ return { epoch, loss: (Math.random() * 20 + 10).toFixed(4), accuracy: "0.0000" };
341
+ }
342
 
343
+ // Loss update: exponential decay towards floor with noise
344
+ const delta = (prevLoss - floorLoss) * effectiveLR * (1 / (1 + epoch / 50)); // Slowdown over epochs
345
+ const noise = (Math.random() - 0.5) * (0.05 / Math.sqrt(epoch + 1));
 
 
 
 
346
  const newLoss = Math.max(floorLoss, prevLoss - delta + noise);
347
+
348
+ // Accuracy: inverse to loss (for classification-like)
349
  const newAcc = Math.min(0.99, 1 - (newLoss / 8) + (Math.random() * 0.01));
350
 
351
+ // Plateau if deep without stability
352
+ if (maxDepth > 10 && vanishingGradientRisk < 0.5 && Math.random() > 0.8) {
353
+ setSimAlert("Training Plateau: Add residuals/norms or reduce depth.");
354
+ }
355
+
356
  return {
357
  epoch,
358
  loss: newLoss.toFixed(4),
359
  accuracy: newAcc.toFixed(4)
360
  };
361
  };
 
362
  useEffect(() => {
363
  let interval;
364
  if (isTraining) {
365
+ const graphMetrics = analyzeGraph();
366
  interval = setInterval(() => {
367
  setTrainingData(prev => {
368
  const nextEpoch = prev.length;
369
+ if (nextEpoch >= maxEpochs) {
370
+ setIsTraining(false);
371
+ return prev;
372
  }
373
+ const prevLoss = prev.length > 0 ? parseFloat(prev[prev.length - 1].loss) : 8.0;
374
+ const step = runTrainingStep(nextEpoch, graphMetrics, prevLoss);
375
  if (!step) {
376
  setIsTraining(false);
377
  return prev;
 
381
  }, 100);
382
  }
383
  return () => clearInterval(interval);
384
+ }, [isTraining, nodes, connections, learningRate, maxEpochs]);
 
385
  // --- INTERACTION HANDLERS ---
386
  const handleWheel = (e) => {
387
  e.preventDefault();
 
389
  const newZoom = Math.min(Math.max(zoom - e.deltaY * zoomSpeed, 0.2), 2);
390
  setZoom(newZoom);
391
  };
 
392
  const addNode = (type) => {
393
  const newNode = {
394
  id: `n${Date.now()}`,
 
399
  };
400
  setNodes([...nodes, newNode]);
401
  };
 
402
  const deleteNode = (id) => {
403
  setNodes(nodes.filter(n => n.id !== id));
404
  setConnections(connections.filter(c => c.from !== id && c.to !== id));
405
  if (selectedNodeId === id) setSelectedNodeId(null);
406
  };
 
407
  const deleteConnection = (id) => {
408
  setConnections(connections.filter(c => c.id !== id));
409
  };
 
410
  const handleCanvasMouseDown = (e) => {
411
  if (e.target === canvasRef.current || e.target.tagName === 'svg' || e.target.id === 'grid-background') {
412
  setIsPanning(true);
 
414
  setSelectedNodeId(null);
415
  }
416
  };
 
417
  const handleNodeMouseDown = (e, id) => {
418
  e.stopPropagation();
419
  if (connectingFrom) return;
 
422
  setDragOffset({ x: e.clientX / zoom - node.x, y: e.clientY / zoom - node.y });
423
  setSelectedNodeId(id);
424
  };
 
425
  const handleMouseMove = (e) => {
426
  if (isDraggingNode) {
427
  setNodes(nodes.map(n => n.id === isDraggingNode ? { ...n, x: e.clientX / zoom - dragOffset.x, y: e.clientY / zoom - dragOffset.y } : n));
 
432
  });
433
  }
434
  };
 
435
  const handleMouseUp = () => {
436
  setIsDraggingNode(null);
437
  setIsPanning(false);
438
  };
 
439
  const startConnection = (e, id) => {
440
  e.stopPropagation();
441
  setConnectingFrom(id);
442
  };
 
443
  const endConnection = (e, id) => {
444
  e.stopPropagation();
445
  if (connectingFrom && connectingFrom !== id) {
 
449
  }
450
  setConnectingFrom(null);
451
  };
 
452
  const updateConfig = (key, val) => {
453
  setNodes(nodes.map(n => n.id === selectedNodeId ? { ...n, config: { ...n.config, [key]: val } } : n));
454
  };
 
455
  const resetView = () => {
456
  setViewOffset({ x: 0, y: 0 });
457
  setZoom(1);
458
  };
 
459
  const selectedNode = nodes.find(n => n.id === selectedNodeId);
 
460
  // Calculated properties for infinite grid
461
  const gridSize = 24 * zoom;
462
  const gridOffsetX = viewOffset.x % gridSize;
463
  const gridOffsetY = viewOffset.y % gridSize;
464
+
465
  const currentEpoch = trainingData.length;
466
  const trainingProgress = (currentEpoch / maxEpochs) * 100;
467
 
468
+ const graphMetrics = analyzeGraph();
469
+ const totalParams = graphMetrics.totalParams ? (graphMetrics.totalParams / 1e6).toFixed(2) + 'M' : 'N/A';
470
+ const isValidGraph = !graphMetrics.error;
471
+
472
  return (
473
  <div className="flex flex-col h-screen w-screen bg-slate-950 text-slate-100 overflow-hidden font-sans">
474
  {/* Header */}
 
477
  <div className="p-2 bg-indigo-500 rounded-lg shadow-[0_0_15px_rgba(99,102,241,0.4)]">
478
  <Layers size={20} className="text-white" />
479
  </div>
480
+ <h1 className="text-lg font-bold tracking-tight">Arch-Sim <span className="text-slate-500 font-normal">v3.0 Realistic</span></h1>
481
  </div>
482
+
483
  <div className="flex items-center gap-4">
484
  <div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
485
  <span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider">LR</span>
486
+ <input
487
+ type="range"
488
+ min="0.0001"
489
+ max="0.05"
490
+ step="0.0001"
491
+ value={learningRate}
492
  onChange={(e) => setLearningRate(parseFloat(e.target.value))}
493
  className="w-20 accent-indigo-500"
494
  />
495
  <span className="text-[11px] font-mono text-indigo-300 w-10">{learningRate}</span>
496
  </div>
 
497
  <div className="flex items-center bg-slate-800 rounded-full px-4 py-1.5 gap-3 border border-slate-700">
498
  <Clock size={12} className="text-slate-400" />
499
  <span className="text-[10px] text-slate-400 uppercase font-bold tracking-wider whitespace-nowrap">Max Epochs</span>
500
+ <input
501
+ type="range"
502
+ min="50"
503
+ max="1000"
504
+ step="50"
505
+ value={maxEpochs}
506
  onChange={(e) => setMaxEpochs(parseInt(e.target.value))}
507
  className="w-20 accent-indigo-500"
508
  />
509
  <span className="text-[11px] font-mono text-indigo-300 w-8">{maxEpochs}</span>
510
  </div>
511
+
512
+ <button
513
  onClick={() => {
514
  if (isTraining) setIsTraining(false);
515
  else {
 
525
  </button>
526
  </div>
527
  </header>
 
528
  <main className="flex-1 flex overflow-hidden">
529
  {/* Sidebar: Blocks Palette */}
530
  <aside className="w-64 border-r border-slate-800 bg-slate-900/30 p-4 flex flex-col gap-6 overflow-y-auto z-10">
 
550
  </div>
551
  </div>
552
  </aside>
 
553
  {/* Canvas Area */}
554
+ <div
555
  className="flex-1 relative bg-slate-950 overflow-hidden cursor-grab active:cursor-grabbing outline-none"
556
  onMouseDown={handleCanvasMouseDown}
557
  onMouseMove={handleMouseMove}
 
560
  ref={canvasRef}
561
  >
562
  {/* Infinite Dot Grid Background */}
563
+ <div
564
  id="grid-background"
565
  className="absolute inset-0 pointer-events-none"
566
+ style={{
567
  backgroundImage: `radial-gradient(circle, #1e293b 1px, transparent 1px)`,
568
  backgroundSize: `${gridSize}px ${gridSize}px`,
569
  backgroundPosition: `${gridOffsetX}px ${gridOffsetY}px`
570
  }}
571
  />
 
572
  {/* Alert Message */}
573
  {simAlert && (
574
  <div className="absolute top-6 left-1/2 -translate-x-1/2 bg-red-500/90 text-white px-4 py-2 rounded-full flex items-center gap-2 text-sm font-bold shadow-2xl z-50 animate-bounce">
 
576
  {simAlert}
577
  </div>
578
  )}
 
579
  {/* Viewport Transform Container */}
580
+ <div
581
  className="absolute inset-0 pointer-events-none"
582
+ style={{
583
  transform: `translate(${viewOffset.x}px, ${viewOffset.y}px) scale(${zoom})`,
584
  transformOrigin: '0 0'
585
  }}
 
595
  const fromNode = nodes.find(n => n.id === conn.from);
596
  const toNode = nodes.find(n => n.id === conn.to);
597
  if (!fromNode || !toNode) return null;
598
+
599
  const x1 = fromNode.x + 160;
600
  const y1 = fromNode.y + 40;
601
  const x2 = toNode.x;
602
  const y2 = toNode.y + 40;
603
  const dx = (x2 - x1) / 2;
604
+
605
  return (
606
  <g key={conn.id} className="group cursor-pointer">
607
  <path
 
625
  );
626
  })}
627
  </svg>
 
628
  {/* Nodes */}
629
  {nodes.map(node => {
630
  const type = BLOCK_TYPES[node.type];
 
648
  </button>
649
  )}
650
  </div>
651
+
652
  <h4 className="text-xs font-bold uppercase tracking-wide truncate">{type.label}</h4>
653
  <div className="text-[10px] text-slate-500 mb-4">{node.id}</div>
 
654
  {type.inputs > 0 && (
655
+ <div
656
  onMouseUp={(e) => endConnection(e, node.id)}
657
  className={`absolute -left-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 ${connectingFrom ? 'border-yellow-400 animate-pulse scale-125' : 'border-indigo-500'} flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30 transition-transform`}
658
  >
 
660
  </div>
661
  )}
662
  {type.outputs > 0 && (
663
+ <div
664
  onMouseDown={(e) => startConnection(e, node.id)}
665
  className="absolute -right-3 top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-slate-800 border-2 border-indigo-500 flex items-center justify-center hover:bg-indigo-500 cursor-pointer z-30"
666
  >
 
671
  );
672
  })}
673
  </div>
 
674
  {/* Navigation Controls Overlay */}
675
  <div className="absolute bottom-6 left-6 flex flex-col gap-2 z-20">
676
  <div className="flex gap-2">
677
+ <button
678
  onClick={resetView}
679
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400 flex items-center gap-2"
680
  >
681
  <Maximize size={18} />
682
  <span className="text-[10px] font-bold uppercase tracking-widest">{Math.round(zoom * 100)}%</span>
683
  </button>
684
+ <button
685
  onClick={() => setZoom(prev => Math.min(prev + 0.1, 2))}
686
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400"
687
  >
688
  <ZoomIn size={18} />
689
  </button>
690
+ <button
691
  onClick={() => setZoom(prev => Math.max(prev - 0.1, 0.2))}
692
  className="p-3 bg-slate-900 border border-slate-700 rounded-lg hover:bg-slate-800 transition-colors shadow-xl text-slate-400"
693
  >
 
696
  </div>
697
  </div>
698
  </div>
 
699
  {/* Right Sidebar: Config & Monitoring */}
700
  <aside className="w-80 border-l border-slate-800 bg-slate-900/50 flex flex-col z-10">
701
  <div className="h-[55%] p-6 flex flex-col border-b border-slate-800 overflow-hidden">
 
707
  Epoch {currentEpoch}/{maxEpochs}
708
  </div>
709
  </div>
 
710
  {/* Progress Bar */}
711
  <div className="w-full h-1.5 bg-slate-800 rounded-full mb-4 overflow-hidden">
712
+ <div
713
+ className="h-full bg-indigo-500 transition-all duration-300 shadow-[0_0_10px_rgba(99,102,241,0.5)]"
714
  style={{ width: `${trainingProgress}%` }}
715
  />
716
  </div>
717
+
718
  <div className="flex-1 bg-slate-900 rounded-xl p-2 border border-slate-800 overflow-hidden relative">
719
  {trainingData.length > 0 ? (
720
  <ResponsiveContainer width="100%" height="100%">
 
722
  <CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
723
  <XAxis dataKey="epoch" hide />
724
  <YAxis hide domain={[0, 'auto']} />
725
+ <Tooltip
726
  contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #1e293b', borderRadius: '8px' }}
727
  itemStyle={{ color: '#6366f1' }}
728
  />
 
731
  </ResponsiveContainer>
732
  ) : (
733
  <div className="h-full flex flex-col items-center justify-center text-slate-600 italic text-xs text-center p-4">
734
+ Simulation idle. Ensure valid graph from INPUT to OUTPUT and click Train.
735
  </div>
736
  )}
737
  </div>
 
738
  <div className="mt-4 grid grid-cols-2 gap-4">
739
  <div className="p-3 bg-slate-900/50 rounded-lg border border-slate-800 text-center">
740
  <div className="text-[10px] uppercase text-slate-500 font-bold mb-1">Loss</div>
 
750
  </div>
751
  </div>
752
  </div>
 
753
  <div className="flex-1 p-6 overflow-y-auto">
754
  <div className="flex items-center gap-2 mb-6 text-slate-400 uppercase text-xs font-bold tracking-widest">
755
  <Settings size={14} /> {selectedNode ? 'Block Parameters' : 'Sim Intelligence'}
756
  </div>
 
757
  {selectedNode ? (
758
  <div className="space-y-6">
759
  <div className="flex items-center gap-3 p-3 bg-indigo-500/10 rounded-xl border border-indigo-500/20">
 
765
  <div className="text-[10px] text-indigo-300 font-mono uppercase">{selectedNode.id}</div>
766
  </div>
767
  </div>
 
768
  <div className="space-y-4">
769
  {Object.entries(selectedNode.config).map(([key, val]) => (
770
  <div key={key} className="space-y-1.5">
 
786
  ) : (
787
  <div className="space-y-4">
788
  <div className="p-4 bg-slate-800/30 rounded-xl border border-slate-700/50 text-xs text-slate-400 space-y-3 leading-relaxed">
789
+ <p><strong className="text-slate-200 block mb-1">Graph Analysis:</strong> Full DAG traversal with dim propagation and param calculation. Supports branches like residuals.</p>
790
+ <p><strong className="text-slate-200 block mb-1">Realism:</strong> Parameter counts, scaling laws for loss floor, gradient stability per block type, bottleneck detection, under/overfit based on params/data ratio.</p>
791
+ <p><strong className="text-slate-200 block mb-1">Tips:</strong> Match dims, add norms/residuals for deep nets, balance params for optimal convergence.</p>
792
  </div>
793
  </div>
794
  )}
795
  </div>
796
  </aside>
797
  </main>
 
798
  <footer className="h-10 border-t border-slate-800 bg-slate-900/80 px-4 flex items-center justify-between text-[10px] text-slate-500 uppercase tracking-widest font-semibold">
799
  <div className="flex gap-4">
800
  <span>Active Nodes: {nodes.length}</span>
801
+ <span>Valid Graph: {isValidGraph ? 'YES' : 'NO'}</span>
802
+ <span>Total Params: {totalParams}</span>
803
  <span>LR: {learningRate}</span>
804
  </div>
805
+ <div>Scientific Arch-Sim Engine v3.0</div>
806
  </footer>
 
807
  <style>{`
808
  @keyframes dash {
809
  to { stroke-dashoffset: -12; }
 
822
  </div>
823
  );
824
  };
 
825
  export default App;