Spaces:
Runtime error
Runtime error
Update app.py
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
| 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]
|
| 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]
|
| 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 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 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
|
| 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 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 588 |
-
engine.
|
| 589 |
-
engine.
|
| 590 |
-
engine.
|
| 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 |
-
|
| 605 |
-
|
| 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
|
| 624 |
-
elif layer == 'upper': engine.n_upper
|
| 625 |
-
elif layer == 'lower': engine.n_lower
|
| 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":
|
| 635 |
-
"n_inputs":
|
| 636 |
-
"
|
| 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):
|
| 654 |
-
|
| 655 |
-
|
| 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 |
-
|
| 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)
|