everydaytok commited on
Commit
1f87580
Β·
verified Β·
1 Parent(s): 137b502

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -164
app.py CHANGED
@@ -15,7 +15,6 @@ CONV_THRESH = 0.02
15
  STIFF_EPS = 0.0005
16
  STIFF_INTERVAL = 1.0
17
 
18
-
19
  # ── ATOMIC UNIT ───────────────────────────────────────────────────────────────
20
 
21
  class HourglassUnit:
@@ -160,7 +159,6 @@ class HourglassUnit:
160
  'n_lower': self.n_lower,
161
  }
162
 
163
-
164
  # ── ENGINE ────────────────────────────────────────────────────────────────────
165
 
166
  class SimEngine:
@@ -196,8 +194,6 @@ class SimEngine:
196
  self.stiffness_history = []
197
  self._init_stack()
198
 
199
- # ── TOPOLOGY ──────────────────────────────────────────────────────────────
200
-
201
  def _init_stack(self):
202
  self.iteration = 0
203
  self.current_error = 0.0
@@ -208,6 +204,8 @@ class SimEngine:
208
  self.connections = []
209
 
210
  n = max(1, self.n_inputs)
 
 
211
  nu = self.n_upper
212
  nl = self.n_lower
213
  arch = self.architecture
@@ -237,8 +235,6 @@ class SimEngine:
237
  self.connections.append({'from_uid': ub, 'to_uid': uid, 'to_port': 'B'})
238
  self.topology.append(curr)
239
 
240
- # ── DATASET ───────────────────────────────────────────────────────────────
241
-
242
  def _to_vec(self, val, n):
243
  if isinstance(val, (list, tuple)):
244
  v = [float(x) for x in val]
@@ -256,15 +252,18 @@ class SimEngine:
256
  else: result.append(round(a + b, 4))
257
  return result
258
 
259
- # ── PROBLEM SETUP ─────────────────────────────────────────────────────────
260
-
261
  def set_problem(self, a, b, c_target=None):
262
  n = max(1, self.n_inputs)
 
 
263
  self._a_vec = self._to_vec(a, n)
264
  self._b_vec = self._to_vec(b, n)
265
  self._c_vec = list(self._to_vec(c_target, n)) if c_target is not None else []
266
-
267
- # ── LOGGING ───────────────────────────────────────────────────────────────
 
 
 
268
 
269
  def add_log(self, msg):
270
  self.logs.insert(0, f"[{self.iteration:05d}] {msg}")
@@ -274,8 +273,6 @@ class SimEngine:
274
  self.running = False; self.batch_queue.clear()
275
  self.logs = []; self._init_stack()
276
 
277
- # ── FORWARD PASS ──────────────────────────────────────────────────────────
278
-
279
  def _forward_pass(self, training):
280
  a_vec = self._a_vec; b_vec = self._b_vec; c_vec = self._c_vec
281
  alpha = self.back_alpha; io = {}
@@ -284,7 +281,7 @@ class SimEngine:
284
  for i, uid in enumerate(self.topology[0]):
285
  av = a_vec[i] if i < len(a_vec) else a_vec[-1]
286
  bv = b_vec[i] if i < len(b_vec) else b_vec[-1]
287
- u = self.units[uid]; u.reset_hidden()
288
  anchor = training and bool(c_vec) and (only_level or self.individual_train)
289
  if anchor:
290
  if self.individual_train and not only_level:
@@ -304,7 +301,7 @@ class SimEngine:
304
  b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
305
  av = io[a_src][2] if a_src in io else 0.0
306
  bv = io[b_src][2] if b_src in io else 0.0
307
- u = self.units[uid]; u.reset_hidden()
308
  is_root = (lv == len(self.topology) - 1)
309
  anchor = training and bool(c_vec) and is_root
310
  c_tgt = c_vec[0] if anchor else 0.0
@@ -318,75 +315,45 @@ class SimEngine:
318
  uid = self.topology[0][0]; u = self.units[uid]
319
  c_in = self._c_vec[0] if self._c_vec else 0.0
320
  a_gt = self._a_vec[0] if self._a_vec else 0.0
321
- u.reset_hidden()
322
  u.elastic_step(c_in, c_in, a_gt if training else 0.0, training, self.back_alpha)
323
  pred, ff = u.feedforward(c_in, c_in)
324
  if training:
325
  u.lms_update(pred - a_gt, c_in, c_in, ff)
326
  return pred, {uid: (c_in, c_in, pred, ff)}
327
 
328
- # ── CREDIT ASSIGNMENT ─────────────────────────────────────────────────────
329
-
330
- def _backprop(self, root_err, io):
331
- errors = {self.topology[-1][-1]: root_err}
332
- for lv in range(len(self.topology)-1, -1, -1):
333
- for uid in self.topology[lv]:
334
- if uid not in errors: continue
335
- err = errors[uid]; av, bv, _, ff = io[uid]
336
- self.units[uid].lms_update(err, av, bv, ff)
337
- if lv > 0:
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  u = self.units[uid]
339
  conns = [c for c in self.connections if c['to_uid'] == uid]
340
  a_src = next((c['from_uid'] for c in conns if c['to_port'] == 'A'), None)
341
  b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
342
- if a_src:
343
- errors[a_src] = errors.get(a_src, 0.0) + err * u.sensitivity_to_a()
344
- if b_src and b_src != a_src:
345
- errors[b_src] = errors.get(b_src, 0.0) + err * u.sensitivity_to_b()
346
-
347
- def _greedy(self, root_err, io):
348
- n = len(self.topology)
349
- for lv in range(n-1, -1, -1):
350
- scale = 1.0 / max(1, n - lv)
351
- for uid in self.topology[lv]:
352
- av, bv, _, ff = io[uid]
353
- self.units[uid].lms_update(root_err * scale, av, bv, ff)
354
-
355
- def _param_hg(self, root_err, io):
356
- for level in self.topology:
357
- for uid in level:
358
- av, bv, _, ff = io[uid]
359
- self.units[uid].lms_update(root_err, av, bv, ff)
360
-
361
- def _independent(self, root_err, io):
362
- errors = {self.topology[-1][-1]: root_err}
363
- for lv in range(len(self.topology)-1, 0, -1):
364
- for uid in self.topology[lv]:
365
- if uid not in errors: continue
366
- err = errors[uid]; av, bv, _, ff = io[uid]
367
- self.units[uid].lms_update(err, av, bv, ff)
368
- u = self.units[uid]
369
- conns = [c for c in self.connections if c['to_uid'] == uid]
370
- a_src = next((c['from_uid'] for c in conns if c['to_port'] == 'A'), None)
371
- b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
372
- if a_src:
373
- errors[a_src] = errors.get(a_src, 0.0) + err * u.sensitivity_to_a()
374
- if b_src and b_src != a_src:
375
- errors[b_src] = errors.get(b_src, 0.0) + err * u.sensitivity_to_b()
376
- # Leaves: independent GT
377
- for i, uid in enumerate(self.topology[0]):
378
- av, bv, c_pred, ff = io[uid]
379
- gt_i = self.ground_truth([av], [bv])[0]
380
- self.units[uid].lms_update(c_pred - gt_i, av, bv, ff)
381
-
382
- def _train(self, root_err, io):
383
- m = self.credit_mode
384
- if m == 'elastic_backprop': self._backprop(root_err, io)
385
- elif m == 'greedy': self._greedy(root_err, io)
386
- elif m == 'param_hg': self._param_hg(root_err, io)
387
- elif m == 'independent': self._independent(root_err, io)
388
-
389
- # ── STIFFNESS MONITOR ─────────────────────────────────────────────────────
390
 
391
  def _update_stiffness(self):
392
  now = time.time()
@@ -399,12 +366,9 @@ class SimEngine:
399
  if len(self.stiffness_history) > 60:
400
  self.stiffness_history.pop(0)
401
 
402
- # ── PHYSICS STEP ──────────────────────────────────────────────────────────
403
-
404
  def physics_step(self):
405
  training = (self.mode == 'training')
406
 
407
- # Reverse mode: C→A reconstruction
408
  if self.reverse_mode:
409
  pred, io = self._forward_reverse(training)
410
  a_gt = self._a_vec[0] if self._a_vec else 0.0
@@ -419,10 +383,8 @@ class SimEngine:
419
  return self._next_or_stop()
420
  return True
421
 
422
- # Normal forward/stacked pass
423
  pred, io = self._forward_pass(training)
424
 
425
- # Flat multi-unit: average error across all leaf outputs vs their own GT
426
  if len(self.topology) == 1 and len(self.topology[0]) > 1:
427
  errors = []
428
  for i, uid in enumerate(self.topology[0]):
@@ -447,7 +409,6 @@ class SimEngine:
447
  self.iteration += 1
448
  return True
449
 
450
- # Stacked: single root output vs single GT
451
  c_gt = self._c_vec[0] if self._c_vec else None
452
  root_err = (pred - c_gt) if c_gt is not None else 0.0
453
  self.current_error = round(abs(root_err), 5)
@@ -458,10 +419,7 @@ class SimEngine:
458
 
459
  if self.current_error < CONV_THRESH:
460
  n_units = len(self.units)
461
- self.add_log(
462
- f"βœ“ STACK L{self.stack_levels} [{n_units}u] "
463
- f"P={pred:.4f} GT={c_gt:.4f} Ξ”={abs(root_err):.4f}"
464
- )
465
  return self._next_or_stop()
466
 
467
  if training and c_gt is not None:
@@ -483,6 +441,8 @@ class SimEngine:
483
  def generate_batch(self, count=30):
484
  self.batch_queue.clear()
485
  n = max(1, self.n_inputs)
 
 
486
  for _ in range(count):
487
  a_vec = [round(random.uniform(1.0, 10.0), 2) for _ in range(n)]
488
  b_vec = [round(random.uniform(1.0, 10.0), 2) for _ in range(n)]
@@ -492,43 +452,31 @@ class SimEngine:
492
  if self.stack_levels == 0:
493
  c_vec = self.ground_truth(a_vec, b_vec)
494
  else:
495
- # Stacked: single output GT is the function of the first pair
496
  c_vec = [self.ground_truth([a_vec[0]], [b_vec[0]])[0]]
497
  self.batch_queue.append({'a': a_vec, 'b': b_vec, 'c': c_vec})
498
  p = self.batch_queue.popleft()
499
  self.set_problem(p['a'], p['b'], p.get('c'))
500
  self.running = True
501
  tag = f'L{self.stack_levels}' if self.stack_levels else 'flat'
502
- self.add_log(
503
- f"β–Ά {count} | {self.dataset_type} | "
504
- f"D={n} U{self.n_upper}Β·L{self.n_lower} "
505
- f"[{tag}|{self.credit_mode[:4]}]"
506
- )
507
 
508
 
509
  # ── SERVER ────────────────────────────────────────────────────────────────────
510
  engine = SimEngine()
511
 
512
-
513
  def run_loop():
514
  while True:
515
- if engine.running:
516
- engine.physics_step()
517
  time.sleep(0.028)
518
 
519
-
520
  threading.Thread(target=run_loop, daemon=True).start()
521
 
522
-
523
  @app.get("/", response_class=HTMLResponse)
524
- async def get_ui():
525
- return FileResponse("index.html")
526
-
527
 
528
  @app.get("/state")
529
  async def get_state():
530
  units_out = {uid: u.to_dict() for uid, u in engine.units.items()}
531
- # Flatten all springs for display (prefixed with unit id)
532
  all_springs = {}
533
  for uid, u in engine.units.items():
534
  short = uid.replace('HG_', '')
@@ -560,124 +508,88 @@ async def get_state():
560
  'queue_size': len(engine.batch_queue),
561
  'stiffness_active': engine.stiffness_active,
562
  'stiffness_history': engine.stiffness_history,
563
- 'a_vec': engine._a_vec,
564
- 'b_vec': engine._b_vec,
565
- 'c_vec': engine._c_vec,
566
  }
567
 
568
-
569
  @app.post("/set_mode")
570
  async def set_mode(data: dict):
571
- engine.mode = data.get('mode', engine.mode)
572
  engine.running = False
573
- engine.add_log(f"Mode β†’ {engine.mode} (springs preserved)")
574
  return {"ok": True}
575
 
576
-
577
  @app.post("/config")
578
  async def config(data: dict):
579
- new_ni = max(1, min(8, int(data.get('n_inputs', engine.n_inputs))))
580
- new_nu = max(1, min(16, int(data.get('n_upper', engine.n_upper))))
581
- new_nl = max(1, min(16, int(data.get('n_lower', engine.n_lower))))
582
- new_sl = max(0, min(4, int(data.get('stack_levels', engine.stack_levels))))
 
 
 
 
 
 
 
583
 
584
  topo_changed = (new_ni != engine.n_inputs or new_nu != engine.n_upper or
585
  new_nl != engine.n_lower or new_sl != engine.stack_levels)
586
 
587
- engine.architecture = data.get('architecture', engine.architecture)
588
- engine.dataset_type = data.get('dataset', engine.dataset_type)
589
- engine.back_alpha = max(0.0, min(1.0, float(data.get('back_alpha', engine.back_alpha))))
590
- engine.credit_mode = data.get('credit_mode', engine.credit_mode)
591
- engine.reverse_mode = bool(data.get('reverse_mode', engine.reverse_mode))
592
- engine.individual_train= bool(data.get('individual_train', engine.individual_train))
593
- engine.n_inputs = new_ni
594
- engine.n_upper = new_nu
595
- engine.n_lower = new_nl
596
- engine.stack_levels = new_sl
597
- if 'mode' in data:
598
- engine.mode = data['mode']
599
 
600
  if topo_changed:
601
  engine.running = False
602
  engine._init_stack()
603
  engine.logs = []
604
- n_units = len(engine.units)
605
- engine.add_log(
606
- f"Stack rebuilt: D={new_ni} U{new_nu}Β·L{new_nl} "
607
- f"levels={new_sl} units={n_units} "
608
- f"credit={engine.credit_mode}"
609
- )
610
- else:
611
- engine.add_log(
612
- f"Config: {engine.mode}|{engine.architecture}"
613
- f"|Ξ±={engine.back_alpha:.2f}|{engine.credit_mode}"
614
- )
615
-
616
- return {"ok": True, "topo_changed": topo_changed, "n_units": len(engine.units)}
617
-
618
 
619
  @app.post("/set_layer")
620
  async def set_layer(data: dict):
621
  layer = data.get('layer', '')
622
  delta = int(data.get('delta', 0))
623
- if layer == 'inputs': engine.n_inputs = max(1, min(8, engine.n_inputs + delta))
624
- elif layer == 'upper': engine.n_upper = max(1, min(16, engine.n_upper + delta))
625
- elif layer == 'lower': engine.n_lower = max(1, min(16, engine.n_lower + delta))
626
- elif layer == 'stack': engine.stack_levels = max(0, min(4, engine.stack_levels + delta))
627
  engine.running = False
628
  engine._init_stack()
629
- engine.add_log(
630
- f"Topology β†’ D={engine.n_inputs} U{engine.n_upper}Β·L{engine.n_lower} "
631
- f"stack={engine.stack_levels} units={len(engine.units)}"
632
- )
633
  return {
634
- "ok": True,
635
- "n_inputs": engine.n_inputs,
636
- "n_upper": engine.n_upper,
637
- "n_lower": engine.n_lower,
638
- "stack_levels": engine.stack_levels,
639
- "n_units": len(engine.units),
640
  }
641
 
642
-
643
  @app.post("/generate")
644
  async def generate(data: dict):
645
  engine.generate_batch(int(data.get('count', 30)))
646
  return {"ok": True}
647
 
648
-
649
  @app.post("/run_custom")
650
  async def run_custom(data: dict):
651
  def parse(v):
652
  if v is None or v in ('', 'null'): return None
653
- if isinstance(v, list): return [float(x) for x in v]
654
- s = str(v)
655
- if ',' in s:
656
- return [float(x.strip()) for x in s.split(',') if x.strip()]
657
- try: return float(s)
658
  except: return None
659
 
660
  a = parse(data.get('a')) or 5.0
661
  b = parse(data.get('b')) or 3.0
 
 
662
  c = parse(data.get('c'))
663
- if c is None and engine.mode == 'training':
664
- c = engine.ground_truth(
665
- engine._to_vec(a, engine.n_inputs),
666
- engine._to_vec(b, engine.n_inputs),
667
- )
668
  engine.batch_queue.clear()
669
  engine.set_problem(a, b, c)
670
  engine.running = True
671
- engine.add_log(f"Custom: A={a} B={b} C={c}")
672
  return {"ok": True}
673
 
674
-
675
  @app.post("/halt")
676
  async def halt():
677
  engine.running = False
678
  return {"ok": True}
679
 
680
-
681
  if __name__ == "__main__":
682
  import uvicorn
683
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
15
  STIFF_EPS = 0.0005
16
  STIFF_INTERVAL = 1.0
17
 
 
18
  # ── ATOMIC UNIT ───────────────────────────────────────────────────────────────
19
 
20
  class HourglassUnit:
 
159
  'n_lower': self.n_lower,
160
  }
161
 
 
162
  # ── ENGINE ────────────────────────────────────────────────────────────────────
163
 
164
  class SimEngine:
 
194
  self.stiffness_history = []
195
  self._init_stack()
196
 
 
 
197
  def _init_stack(self):
198
  self.iteration = 0
199
  self.current_error = 0.0
 
204
  self.connections = []
205
 
206
  n = max(1, self.n_inputs)
207
+ if self.stack_levels > 0: n = 2 ** self.stack_levels
208
+
209
  nu = self.n_upper
210
  nl = self.n_lower
211
  arch = self.architecture
 
235
  self.connections.append({'from_uid': ub, 'to_uid': uid, 'to_port': 'B'})
236
  self.topology.append(curr)
237
 
 
 
238
  def _to_vec(self, val, n):
239
  if isinstance(val, (list, tuple)):
240
  v = [float(x) for x in val]
 
252
  else: result.append(round(a + b, 4))
253
  return result
254
 
 
 
255
  def set_problem(self, a, b, c_target=None):
256
  n = max(1, self.n_inputs)
257
+ if self.stack_levels > 0: n = 2 ** self.stack_levels
258
+
259
  self._a_vec = self._to_vec(a, n)
260
  self._b_vec = self._to_vec(b, n)
261
  self._c_vec = list(self._to_vec(c_target, n)) if c_target is not None else []
262
+
263
+ # PROPERLY RESET PHYSICS ONLY ONCE PER PROBLEM
264
+ for u in self.units.values():
265
+ u.reset_hidden()
266
+ self.current_error = 999.0 # Force out of convergence
267
 
268
  def add_log(self, msg):
269
  self.logs.insert(0, f"[{self.iteration:05d}] {msg}")
 
273
  self.running = False; self.batch_queue.clear()
274
  self.logs = []; self._init_stack()
275
 
 
 
276
  def _forward_pass(self, training):
277
  a_vec = self._a_vec; b_vec = self._b_vec; c_vec = self._c_vec
278
  alpha = self.back_alpha; io = {}
 
281
  for i, uid in enumerate(self.topology[0]):
282
  av = a_vec[i] if i < len(a_vec) else a_vec[-1]
283
  bv = b_vec[i] if i < len(b_vec) else b_vec[-1]
284
+ u = self.units[uid]
285
  anchor = training and bool(c_vec) and (only_level or self.individual_train)
286
  if anchor:
287
  if self.individual_train and not only_level:
 
301
  b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
302
  av = io[a_src][2] if a_src in io else 0.0
303
  bv = io[b_src][2] if b_src in io else 0.0
304
+ u = self.units[uid]
305
  is_root = (lv == len(self.topology) - 1)
306
  anchor = training and bool(c_vec) and is_root
307
  c_tgt = c_vec[0] if anchor else 0.0
 
315
  uid = self.topology[0][0]; u = self.units[uid]
316
  c_in = self._c_vec[0] if self._c_vec else 0.0
317
  a_gt = self._a_vec[0] if self._a_vec else 0.0
 
318
  u.elastic_step(c_in, c_in, a_gt if training else 0.0, training, self.back_alpha)
319
  pred, ff = u.feedforward(c_in, c_in)
320
  if training:
321
  u.lms_update(pred - a_gt, c_in, c_in, ff)
322
  return pred, {uid: (c_in, c_in, pred, ff)}
323
 
324
+ def _train(self, root_err, io):
325
+ m = self.credit_mode
326
+ if m == 'elastic_backprop':
327
+ errors = {self.topology[-1][-1]: root_err}
328
+ for lv in range(len(self.topology)-1, -1, -1):
329
+ for uid in self.topology[lv]:
330
+ if uid not in errors: continue
331
+ err = errors[uid]; av, bv, _, ff = io[uid]
332
+ self.units[uid].lms_update(err, av, bv, ff)
333
+ if lv > 0:
334
+ u = self.units[uid]
335
+ conns = [c for c in self.connections if c['to_uid'] == uid]
336
+ a_src = next((c['from_uid'] for c in conns if c['to_port'] == 'A'), None)
337
+ b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
338
+ if a_src: errors[a_src] = errors.get(a_src, 0.0) + err * u.sensitivity_to_a()
339
+ if b_src and b_src != a_src: errors[b_src] = errors.get(b_src, 0.0) + err * u.sensitivity_to_b()
340
+ elif m == 'independent':
341
+ errors = {self.topology[-1][-1]: root_err}
342
+ for lv in range(len(self.topology)-1, 0, -1):
343
+ for uid in self.topology[lv]:
344
+ if uid not in errors: continue
345
+ err = errors[uid]; av, bv, _, ff = io[uid]
346
+ self.units[uid].lms_update(err, av, bv, ff)
347
  u = self.units[uid]
348
  conns = [c for c in self.connections if c['to_uid'] == uid]
349
  a_src = next((c['from_uid'] for c in conns if c['to_port'] == 'A'), None)
350
  b_src = next((c['from_uid'] for c in conns if c['to_port'] == 'B'), None)
351
+ if a_src: errors[a_src] = errors.get(a_src, 0.0) + err * u.sensitivity_to_a()
352
+ if b_src and b_src != a_src: errors[b_src] = errors.get(b_src, 0.0) + err * u.sensitivity_to_b()
353
+ for i, uid in enumerate(self.topology[0]):
354
+ av, bv, c_pred, ff = io[uid]
355
+ gt_i = self.ground_truth([av], [bv])[0]
356
+ self.units[uid].lms_update(c_pred - gt_i, av, bv, ff)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
 
358
  def _update_stiffness(self):
359
  now = time.time()
 
366
  if len(self.stiffness_history) > 60:
367
  self.stiffness_history.pop(0)
368
 
 
 
369
  def physics_step(self):
370
  training = (self.mode == 'training')
371
 
 
372
  if self.reverse_mode:
373
  pred, io = self._forward_reverse(training)
374
  a_gt = self._a_vec[0] if self._a_vec else 0.0
 
383
  return self._next_or_stop()
384
  return True
385
 
 
386
  pred, io = self._forward_pass(training)
387
 
 
388
  if len(self.topology) == 1 and len(self.topology[0]) > 1:
389
  errors = []
390
  for i, uid in enumerate(self.topology[0]):
 
409
  self.iteration += 1
410
  return True
411
 
 
412
  c_gt = self._c_vec[0] if self._c_vec else None
413
  root_err = (pred - c_gt) if c_gt is not None else 0.0
414
  self.current_error = round(abs(root_err), 5)
 
419
 
420
  if self.current_error < CONV_THRESH:
421
  n_units = len(self.units)
422
+ self.add_log(f"βœ“ STACK L{self.stack_levels} [{n_units}u] P={pred:.4f} GT={c_gt:.4f} Ξ”={abs(root_err):.4f}")
 
 
 
423
  return self._next_or_stop()
424
 
425
  if training and c_gt is not None:
 
441
  def generate_batch(self, count=30):
442
  self.batch_queue.clear()
443
  n = max(1, self.n_inputs)
444
+ if self.stack_levels > 0: n = 2 ** self.stack_levels
445
+
446
  for _ in range(count):
447
  a_vec = [round(random.uniform(1.0, 10.0), 2) for _ in range(n)]
448
  b_vec = [round(random.uniform(1.0, 10.0), 2) for _ in range(n)]
 
452
  if self.stack_levels == 0:
453
  c_vec = self.ground_truth(a_vec, b_vec)
454
  else:
 
455
  c_vec = [self.ground_truth([a_vec[0]], [b_vec[0]])[0]]
456
  self.batch_queue.append({'a': a_vec, 'b': b_vec, 'c': c_vec})
457
  p = self.batch_queue.popleft()
458
  self.set_problem(p['a'], p['b'], p.get('c'))
459
  self.running = True
460
  tag = f'L{self.stack_levels}' if self.stack_levels else 'flat'
461
+ self.add_log(f"β–Ά {count} | D={n} | [{tag}|{self.credit_mode[:4]}]")
 
 
 
 
462
 
463
 
464
  # ── SERVER ────────────────────────────────────────────────────────────────────
465
  engine = SimEngine()
466
 
 
467
  def run_loop():
468
  while True:
469
+ if engine.running: engine.physics_step()
 
470
  time.sleep(0.028)
471
 
 
472
  threading.Thread(target=run_loop, daemon=True).start()
473
 
 
474
  @app.get("/", response_class=HTMLResponse)
475
+ async def get_ui(): return FileResponse("index.html")
 
 
476
 
477
  @app.get("/state")
478
  async def get_state():
479
  units_out = {uid: u.to_dict() for uid, u in engine.units.items()}
 
480
  all_springs = {}
481
  for uid, u in engine.units.items():
482
  short = uid.replace('HG_', '')
 
508
  'queue_size': len(engine.batch_queue),
509
  'stiffness_active': engine.stiffness_active,
510
  'stiffness_history': engine.stiffness_history,
 
 
 
511
  }
512
 
 
513
  @app.post("/set_mode")
514
  async def set_mode(data: dict):
515
+ engine.mode = data.get('mode', engine.mode)
516
  engine.running = False
 
517
  return {"ok": True}
518
 
 
519
  @app.post("/config")
520
  async def config(data: dict):
521
+ engine.architecture = data.get('architecture', engine.architecture)
522
+ engine.dataset_type = data.get('dataset', engine.dataset_type)
523
+ engine.back_alpha = max(0.0, min(1.0, float(data.get('back_alpha', engine.back_alpha))))
524
+ engine.credit_mode = data.get('credit_mode', engine.credit_mode)
525
+ engine.reverse_mode = bool(data.get('reverse_mode', engine.reverse_mode))
526
+ engine.individual_train= bool(data.get('individual_train', engine.individual_train))
527
+
528
+ new_ni = max(1, min(16, int(data.get('n_inputs', engine.n_inputs))))
529
+ new_nu = max(1, min(16, int(data.get('n_upper', engine.n_upper))))
530
+ new_nl = max(1, min(16, int(data.get('n_lower', engine.n_lower))))
531
+ new_sl = max(0, min(4, int(data.get('stack_levels', engine.stack_levels))))
532
 
533
  topo_changed = (new_ni != engine.n_inputs or new_nu != engine.n_upper or
534
  new_nl != engine.n_lower or new_sl != engine.stack_levels)
535
 
536
+ engine.n_inputs = new_ni
537
+ engine.n_upper = new_nu
538
+ engine.n_lower = new_nl
539
+ engine.stack_levels = new_sl
 
 
 
 
 
 
 
 
540
 
541
  if topo_changed:
542
  engine.running = False
543
  engine._init_stack()
544
  engine.logs = []
545
+
546
+ return {"ok": True}
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
  @app.post("/set_layer")
549
  async def set_layer(data: dict):
550
  layer = data.get('layer', '')
551
  delta = int(data.get('delta', 0))
552
+ if layer == 'inputs': engine.n_inputs = max(1, min(16, engine.n_inputs + delta))
553
+ elif layer == 'upper': engine.n_upper = max(1, min(16, engine.n_upper + delta))
554
+ elif layer == 'lower': engine.n_lower = max(1, min(16, engine.n_lower + delta))
 
555
  engine.running = False
556
  engine._init_stack()
 
 
 
 
557
  return {
558
+ "ok": True,
559
+ "n_inputs": engine.n_inputs, "n_upper": engine.n_upper,
560
+ "n_lower": engine.n_lower, "stack_levels": engine.stack_levels
 
 
 
561
  }
562
 
 
563
  @app.post("/generate")
564
  async def generate(data: dict):
565
  engine.generate_batch(int(data.get('count', 30)))
566
  return {"ok": True}
567
 
 
568
  @app.post("/run_custom")
569
  async def run_custom(data: dict):
570
  def parse(v):
571
  if v is None or v in ('', 'null'): return None
572
+ if isinstance(v, list): return [float(x) for x in v]
573
+ if ',' in str(v): return [float(x.strip()) for x in str(v).split(',') if x.strip()]
574
+ try: return float(v)
 
 
575
  except: return None
576
 
577
  a = parse(data.get('a')) or 5.0
578
  b = parse(data.get('b')) or 3.0
579
+
580
+ # We now strictly trust the C target calculated and sent by the UI
581
  c = parse(data.get('c'))
582
+
 
 
 
 
583
  engine.batch_queue.clear()
584
  engine.set_problem(a, b, c)
585
  engine.running = True
 
586
  return {"ok": True}
587
 
 
588
  @app.post("/halt")
589
  async def halt():
590
  engine.running = False
591
  return {"ok": True}
592
 
 
593
  if __name__ == "__main__":
594
  import uvicorn
595
  uvicorn.run(app, host="0.0.0.0", port=7860)