TwoQuarks commited on
Commit
87df894
·
verified ·
1 Parent(s): db7550f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +232 -79
app.py CHANGED
@@ -42,6 +42,13 @@ def _plot_series(y, title, xlabel='step', ylabel='value'):
42
  return fig
43
 
44
 
 
 
 
 
 
 
 
45
  # =========================
46
  # DOWN / AntiDown
47
  # =========================
@@ -104,9 +111,14 @@ def run_down(episodes_per_phase: int, seed: int):
104
  'artifact_csv': str(out_csv),
105
  }
106
 
107
- headers = ['agent', 'phase', 'mean_total_reward', 'std_total_reward', 'mean_valley_visits', 'n']
 
 
 
 
 
108
  rows = [[row.get(h) for h in headers] for row in table_lines]
109
- return json.dumps(meta, indent=2), rows, fig, str(out_csv)
110
 
111
 
112
  def run_antidown(n_episodes: int, base_seed: int):
@@ -193,9 +205,14 @@ def run_antidown(n_episodes: int, base_seed: int):
193
  'artifact_csv': str(out_csv),
194
  }
195
 
196
- headers = ['agent', 'phase', 'mean_reward', 'std_reward', 'n']
 
 
 
 
 
197
  rows = [[row.get(h) for h in headers] for row in table_lines]
198
- return json.dumps(meta, indent=2), rows, fig, str(out_csv)
199
 
200
 
201
  # =========================
@@ -243,7 +260,12 @@ def run_strange(n_episodes: int, max_steps: int, seed: int, mode: str):
243
  'artifact_csv': str(out_csv),
244
  }
245
 
246
- return json.dumps(metrics, indent=2), fig, str(out_csv)
 
 
 
 
 
247
 
248
 
249
  # =========================
@@ -283,7 +305,14 @@ def run_charm(n_episodes: int, seed: int):
283
  'artifact_csv': str(out_csv),
284
  }
285
 
286
- return json.dumps(metrics, indent=2), fig_r, fig_l, fig_rho, str(out_csv)
 
 
 
 
 
 
 
287
 
288
 
289
  # =========================
@@ -291,87 +320,210 @@ def run_charm(n_episodes: int, seed: int):
291
  # =========================
292
 
293
  def build_ui():
294
- with gr.Blocks(title='TwoQuarks QuarksLab (Interactive)') as demo:
295
- gr.Markdown(
296
- """# TwoQuarks • QuarksLab (Interactive)
297
-
298
- Real runs, real knobs: seed/episodes/steps. Short-bounded experiments (public CPU).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  """
300
- )
301
-
302
- with gr.Tab('DOWN / AntiDown'):
303
- gr.Markdown('Tabular experiments (fast).')
304
- with gr.Row():
305
- down_eps = gr.Slider(50, 800, value=200, step=50, label='DOWN: episodes_per_phase')
306
- down_seed = gr.Number(value=2025, precision=0, label='DOWN: seed')
307
- run_down_btn = gr.Button('Run DOWN')
308
- down_meta = gr.Code(label='DOWN: run meta (JSON)', language='json')
309
- down_table = gr.Dataframe(
310
- headers=['agent', 'phase', 'mean_reward', 'std_reward', 'n'],
311
- label='DOWN: summary (mean/std by phase+agent)',
312
- interactive=False,
313
- )
314
- down_plot = gr.Plot(label='DOWN: mean reward by phase')
315
- down_file = gr.File(label='DOWN: results CSV')
316
 
317
- run_down_btn.click(
318
- fn=run_down,
319
- inputs=[down_eps, down_seed],
320
- outputs=[down_meta, down_table, down_plot, down_file],
321
- )
322
-
323
- gr.Markdown('---')
324
- with gr.Row():
325
- ad_eps = gr.Slider(20, 400, value=120, step=20, label='AntiDown: episodes_per_phase')
326
- ad_seed = gr.Number(value=1234, precision=0, label='AntiDown: base seed')
327
- run_ad_btn = gr.Button('Run AntiDown')
328
- ad_meta = gr.Code(label='AntiDown: run meta (JSON)', language='json')
329
- ad_table = gr.Dataframe(
330
- headers=['agent', 'phase', 'mean_total_reward', 'std_total_reward', 'mean_valley_visits', 'n'],
331
- label='AntiDown: summary',
332
- interactive=False,
333
- )
334
- ad_plot = gr.Plot(label='AntiDown: mean total reward by phase')
335
- ad_file = gr.File(label='AntiDown: results CSV')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
 
337
- run_ad_btn.click(
338
- fn=run_antidown,
339
- inputs=[ad_eps, ad_seed],
340
- outputs=[ad_meta, ad_table, ad_plot, ad_file],
 
 
 
 
 
 
 
341
  )
342
 
343
- with gr.Tab('STRANGE / AntiStrange'):
344
- gr.Markdown('HypothesisLab: swarm dynamics with hidden regime shifts.')
345
- mode = gr.Radio(['Strange', 'AntiStrange'], value='Strange', label='Mode')
346
- with gr.Row():
347
- seps = gr.Slider(50, 500, value=200, step=50, label='Episodes')
348
- ssteps = gr.Slider(10, 120, value=50, step=5, label='Max steps per episode')
349
- sseed = gr.Number(value=0, precision=0, label='Seed')
350
- run_s_btn = gr.Button('Run')
351
- s_meta = gr.Code(label='Run meta (JSON)', language='json')
352
- s_plot = gr.Plot(label='Return per episode')
353
- s_file = gr.File(label='Results CSV')
354
- run_s_btn.click(fn=run_strange, inputs=[seps, ssteps, sseed, mode], outputs=[s_meta, s_plot, s_file])
355
-
356
- with gr.Tab('CHARM'):
357
- gr.Markdown('Enchanted Valley: non-stationary graph + CharmField meta-control.')
358
- with gr.Row():
359
- ceps = gr.Slider(50, 600, value=300, step=50, label='Episodes')
360
- cseed = gr.Number(value=0, precision=0, label='Seed')
361
- run_c_btn = gr.Button('Run CHARM')
362
- c_meta = gr.Code(label='Run meta (JSON)', language='json')
363
- c_plot_r = gr.Plot(label='Reward')
364
- c_plot_l = gr.Plot(label='Lambda')
365
- c_plot_rho = gr.Plot(label='Rho_mean')
366
- c_file = gr.File(label='Timeseries CSV')
367
- run_c_btn.click(fn=run_charm, inputs=[ceps, cseed], outputs=[c_meta, c_plot_r, c_plot_l, c_plot_rho, c_file])
368
-
369
- gr.Markdown(
370
- """### Notes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  - This Space runs bounded experiments on shared CPU.
372
  - For heavy runs, keep episodes modest and use the CSV artifact to reproduce locally.
373
  """
374
- )
375
 
376
  return demo
377
 
@@ -384,3 +536,4 @@ if __name__ == '__main__':
384
  server_port=int(os.getenv('PORT', '7860')),
385
  show_api=False,
386
  )
 
 
42
  return fig
43
 
44
 
45
+ def _save_fig(fig, path: Path) -> str:
46
+ """Save a matplotlib figure as PNG and return the filepath (string)."""
47
+ path.parent.mkdir(parents=True, exist_ok=True)
48
+ fig.savefig(path, dpi=160, bbox_inches='tight')
49
+ return str(path)
50
+
51
+
52
  # =========================
53
  # DOWN / AntiDown
54
  # =========================
 
111
  'artifact_csv': str(out_csv),
112
  }
113
 
114
+ # Save plot(s) to PNG for the UI carousel
115
+ graphics_dir = out_dir / 'graphics'
116
+ mean_plot = _save_fig(fig, graphics_dir / 'down_mean_reward_by_phase.png')
117
+ carousel = [mean_plot]
118
+
119
+ headers = ['agent', 'phase', 'mean_reward', 'std_reward', 'n']
120
  rows = [[row.get(h) for h in headers] for row in table_lines]
121
+ return json.dumps(meta, indent=2), rows, fig, str(out_csv), carousel
122
 
123
 
124
  def run_antidown(n_episodes: int, base_seed: int):
 
205
  'artifact_csv': str(out_csv),
206
  }
207
 
208
+ # Save plot(s) to PNG for the UI carousel
209
+ graphics_dir = out_dir / 'graphics'
210
+ mean_plot = _save_fig(fig, graphics_dir / 'antidown_mean_total_reward_by_phase.png')
211
+ carousel = [mean_plot]
212
+
213
+ headers = ['agent', 'phase', 'mean_total_reward', 'std_total_reward', 'mean_valley_visits', 'n']
214
  rows = [[row.get(h) for h in headers] for row in table_lines]
215
+ return json.dumps(meta, indent=2), rows, fig, str(out_csv), carousel
216
 
217
 
218
  # =========================
 
260
  'artifact_csv': str(out_csv),
261
  }
262
 
263
+ # Save plot(s) to PNG for the UI carousel
264
+ graphics_dir = out_dir / 'graphics'
265
+ plot_path = _save_fig(fig, graphics_dir / f"{mode.lower()}_return_per_episode.png")
266
+ carousel = [plot_path]
267
+
268
+ return json.dumps(metrics, indent=2), fig, str(out_csv), carousel
269
 
270
 
271
  # =========================
 
305
  'artifact_csv': str(out_csv),
306
  }
307
 
308
+ # Save plots to PNG for the UI carousel
309
+ graphics_dir = out_dir / 'graphics'
310
+ p_r = _save_fig(fig_r, graphics_dir / 'charm_reward.png')
311
+ p_l = _save_fig(fig_l, graphics_dir / 'charm_lambda.png')
312
+ p_rho = _save_fig(fig_rho, graphics_dir / 'charm_rho_mean.png')
313
+ carousel = [p_r, p_l, p_rho]
314
+
315
+ return json.dumps(metrics, indent=2), fig_r, fig_l, fig_rho, str(out_csv), carousel
316
 
317
 
318
  # =========================
 
320
  # =========================
321
 
322
  def build_ui():
323
+ css_path = ROOT / "style.css"
324
+ css = css_path.read_text(encoding="utf-8") if css_path.exists() else ""
325
+
326
+ NAV_HTML = """
327
+ <nav id="tqNav">
328
+ <div class="nav-inner">
329
+ <div class="brand">TwoQuarks</div>
330
+ <div class="nav-links">
331
+ <a href="#down" onclick="return false;">DOWN</a>
332
+ <a href="#strange" onclick="return false;">STRANGE</a>
333
+ <a href="#top" onclick="return false;">TOP</a>
334
+ <a href="#charm" onclick="return false;">CHARM</a>
335
+ <a href="#up" onclick="return false;">UP</a>
336
+ <a href="#bottom" onclick="return false;">BOTTOM</a>
337
+ </div>
338
+ <div class="spacer"></div>
339
+ <div class="menu-btn" id="menuBtn" title="Menu"><span></span></div>
340
+ <div class="menu" id="siteMenu">
341
+ <a href="https://twoquarks.com/#about" target="_blank" rel="noopener">ABOUT</a>
342
+ <a href="https://twoquarks.com/quarkslab.html" target="_blank" rel="noopener">QuarksLab</a>
343
+ <div class="sep"></div>
344
+ <a href="https://twoquarks.com/quarks/bottom/resume.pdf" target="_blank" rel="noopener">Resume</a>
345
+ <a href="https://twoquarks.com/summary.pdf" target="_blank" rel="noopener">Summary</a>
346
+ </div>
347
+ </div>
348
+ </nav>
349
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
351
+ head = """
352
+ <link rel="preconnect" href="https://fonts.googleapis.com">
353
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
354
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
355
+
356
+ <canvas id="quantumField"></canvas>
357
+
358
+ <script>
359
+ (function(){
360
+ // Hamburger menu toggle
361
+ window.addEventListener("load", () => {
362
+ const menuBtn = document.getElementById('menuBtn');
363
+ const siteMenu = document.getElementById('siteMenu');
364
+ if(menuBtn && siteMenu){
365
+ menuBtn.addEventListener('click', (e)=>{ e.stopPropagation(); siteMenu.classList.toggle('open'); });
366
+ document.addEventListener('click', ()=> siteMenu.classList.remove('open'));
367
+ siteMenu.addEventListener('click', (e)=> e.stopPropagation());
368
+ }
369
+ });
370
+
371
+ // Starfield (ported from your site canvas pattern)
372
+ const canvas = document.getElementById('quantumField');
373
+ if(!canvas) return;
374
+ const ctx = canvas.getContext('2d');
375
+
376
+ function resize(){
377
+ canvas.width = innerWidth;
378
+ canvas.height = innerHeight;
379
+ }
380
+ resize();
381
+ addEventListener('resize', resize);
382
+
383
+ let particles = [];
384
+ const count = 680;
385
+
386
+ for(let i=0;i<count;i++){
387
+ particles.push({
388
+ x:(Math.random()-0.5)*canvas.width,
389
+ y:(Math.random()-0.5)*canvas.height,
390
+ z:Math.random()*canvas.width,
391
+ });
392
+ }
393
+
394
+ function render(){
395
+ ctx.clearRect(0,0,canvas.width,canvas.height);
396
+ for(const p of particles){
397
+ p.z -= 2.2;
398
+ if(p.z < 1){
399
+ p.x=(Math.random()-0.7)*canvas.width;
400
+ p.y=(Math.random()-0.7)*canvas.height;
401
+ p.z=canvas.width;
402
+ }
403
+ const k=128/p.z;
404
+ const px=p.x*k+canvas.width/2;
405
+ const py=p.y*k+canvas.height/2;
406
+ const size=(1-p.z/canvas.width)*1.29;
407
+ ctx.beginPath();
408
+ ctx.fillStyle="rgba(140,180,255,0.85)";
409
+ ctx.arc(px,py,size,0,Math.PI*2);
410
+ ctx.fill();
411
+ }
412
+ requestAnimationFrame(render);
413
+ }
414
+ render();
415
+ })();
416
+ </script>
417
+ """
418
 
419
+ with gr.Blocks(title='TwoQuarks - QuarksLab (Interactive)', css=css, head=head) as demo:
420
+ gr.HTML(NAV_HTML)
421
+
422
+ with gr.Column(elem_id='tqApp'):
423
+ gr.HTML(
424
+ """
425
+ <div class="tq-hero">
426
+ <div class="brand">TwoQuarks - QuarksLab (Interactive)</div>
427
+ <div class="desc">Real runs, real knobs: seed/episodes/steps. Short-bounded experiments (public CPU).</div>
428
+ </div>
429
+ """
430
  )
431
 
432
+ with gr.Tab('DOWN / AntiDown'):
433
+ gr.Markdown('Tabular experiments (fast).')
434
+
435
+ with gr.Row():
436
+ down_eps = gr.Slider(50, 800, value=200, step=50, label='DOWN: episodes_per_phase')
437
+ down_seed = gr.Number(value=2025, precision=0, label='DOWN: seed')
438
+ run_down_btn = gr.Button('Run DOWN')
439
+
440
+ down_meta = gr.Code(label='DOWN: run meta (JSON)', language='json')
441
+ down_table = gr.Dataframe(
442
+ headers=['agent', 'phase', 'mean_reward', 'std_reward', 'n'],
443
+ label='DOWN: summary (mean/std by phase+agent)',
444
+ interactive=False,
445
+ )
446
+ down_plot = gr.Plot(label='DOWN: mean reward by phase')
447
+ down_carousel = gr.Gallery(label='DOWN: results (carousel)', columns=1, height=520)
448
+ down_file = gr.File(label='DOWN: results CSV')
449
+
450
+ run_down_btn.click(
451
+ fn=run_down,
452
+ inputs=[down_eps, down_seed],
453
+ outputs=[down_meta, down_table, down_plot, down_file, down_carousel],
454
+ )
455
+
456
+ gr.Markdown('---')
457
+
458
+ with gr.Row():
459
+ ad_eps = gr.Slider(20, 400, value=120, step=20, label='AntiDown: episodes_per_phase')
460
+ ad_seed = gr.Number(value=1234, precision=0, label='AntiDown: base seed')
461
+ run_ad_btn = gr.Button('Run AntiDown')
462
+
463
+ ad_meta = gr.Code(label='AntiDown: run meta (JSON)', language='json')
464
+ ad_table = gr.Dataframe(
465
+ headers=['agent', 'phase', 'mean_total_reward', 'std_total_reward', 'mean_valley_visits', 'n'],
466
+ label='AntiDown: summary',
467
+ interactive=False,
468
+ )
469
+ ad_plot = gr.Plot(label='AntiDown: mean total reward by phase')
470
+ ad_carousel = gr.Gallery(label='AntiDown: results (carousel)', columns=1, height=520)
471
+ ad_file = gr.File(label='AntiDown: results CSV')
472
+
473
+ run_ad_btn.click(
474
+ fn=run_antidown,
475
+ inputs=[ad_eps, ad_seed],
476
+ outputs=[ad_meta, ad_table, ad_plot, ad_file, ad_carousel],
477
+ )
478
+
479
+ with gr.Tab('STRANGE / AntiStrange'):
480
+ gr.Markdown('HypothesisLab: swarm dynamics with hidden regime shifts.')
481
+ mode = gr.Radio(['Strange', 'AntiStrange'], value='Strange', label='Mode')
482
+
483
+ with gr.Row():
484
+ seps = gr.Slider(50, 500, value=200, step=50, label='Episodes')
485
+ ssteps = gr.Slider(10, 120, value=50, step=5, label='Max steps per episode')
486
+ sseed = gr.Number(value=0, precision=0, label='Seed')
487
+
488
+ run_s_btn = gr.Button('Run')
489
+ s_meta = gr.Code(label='Run meta (JSON)', language='json')
490
+ s_plot = gr.Plot(label='Return per episode')
491
+ s_carousel = gr.Gallery(label='Results (carousel)', columns=1, height=520)
492
+ s_file = gr.File(label='Results CSV')
493
+
494
+ run_s_btn.click(
495
+ fn=run_strange,
496
+ inputs=[seps, ssteps, sseed, mode],
497
+ outputs=[s_meta, s_plot, s_file, s_carousel],
498
+ )
499
+
500
+ with gr.Tab('CHARM'):
501
+ gr.Markdown('Enchanted Valley: non-stationary graph + CharmField meta-control.')
502
+
503
+ with gr.Row():
504
+ ceps = gr.Slider(50, 600, value=300, step=50, label='Episodes')
505
+ cseed = gr.Number(value=0, precision=0, label='Seed')
506
+ run_c_btn = gr.Button('Run CHARM')
507
+
508
+ c_meta = gr.Code(label='Run meta (JSON)', language='json')
509
+ c_plot_r = gr.Plot(label='Reward')
510
+ c_plot_l = gr.Plot(label='Lambda')
511
+ c_plot_rho = gr.Plot(label='Rho_mean')
512
+ c_carousel = gr.Gallery(label='CHARM: results (carousel)', columns=1, height=520)
513
+ c_file = gr.File(label='Timeseries CSV')
514
+
515
+ run_c_btn.click(
516
+ fn=run_charm,
517
+ inputs=[ceps, cseed],
518
+ outputs=[c_meta, c_plot_r, c_plot_l, c_plot_rho, c_file, c_carousel],
519
+ )
520
+
521
+ gr.Markdown(
522
+ """### Notes
523
  - This Space runs bounded experiments on shared CPU.
524
  - For heavy runs, keep episodes modest and use the CSV artifact to reproduce locally.
525
  """
526
+ )
527
 
528
  return demo
529
 
 
536
  server_port=int(os.getenv('PORT', '7860')),
537
  show_api=False,
538
  )
539
+