File size: 3,920 Bytes
603b5b4
 
 
 
8071c8f
603b5b4
 
 
 
 
8071c8f
603b5b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8071c8f
 
 
 
 
 
 
603b5b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { useState } from "react";
import AlgorithmControls, { DEFAULT_HYPERPARAMETERS } from "./AlgorithmControls";
import type { ComparisonEntry, ComparisonTrajectory, SettingsUi } from "./types";

export default function ComparePanel({ settings, entries, trajectories, onAdd, onRemove, onToggleVisibility, iteration, busy, ready, animating, onAction }: {
  settings: SettingsUi;
  entries: ComparisonEntry[];
  trajectories: ComparisonTrajectory[];
  onAdd: (settings: SettingsUi) => void;
  onRemove: (id: number) => void;
  onToggleVisibility: (id: number) => void;
  iteration: number;
  busy: boolean;
  ready: boolean;
  animating: boolean;
  onAction: (action: string) => void;
}) {
  const [draft, setDraft] = useState(settings);
  const configured: SettingsUi = { algorithm: draft.algorithm, x0: draft.x0, y0: draft.y0,
    mode: settings.mode, functionExpr: settings.functionExpr,
    ...Object.fromEntries(Object.entries(DEFAULT_HYPERPARAMETERS[draft.algorithm]).map(([key, fallback]) => [key, draft[key as keyof SettingsUi] ?? fallback])) };
  const valid = [configured.x0, ...(configured.mode === "Bivariate" ? [configured.y0] : []),
    ...Object.entries(configured).filter(([key]) => ["learningRate", "momentum", "beta", "beta1", "beta2", "epsilon"].includes(key)).map(([, value]) => value)
  ].every(value => value !== undefined && value.trim() !== "" && Number.isFinite(Number(value)));
  return <>
    <AlgorithmControls settings={configured} setSettings={setDraft} onRandomInitialPoint={() => setDraft({ ...configured, x0: (Math.random() * 2 - 1).toFixed(2), y0: (Math.random() * 2 - 1).toFixed(2) })} />
    <button className="rounded border border-gray-300 bg-orange-200 px-3 py-1 disabled:opacity-50" disabled={!valid || !ready || busy || animating} onClick={() => onAdd(configured)}>Add</button>
    <div className="text-sm font-semibold">Selected algorithms</div>
    {entries.length === 0 && <p className="text-sm text-stone-500">Add algorithms to compare their trajectories on the same objective.</p>}
    <ul className="flex flex-col gap-2">
      {entries.map((entry, index) => <li key={entry.id} className="rounded border border-stone-300 p-2 text-sm">
        <div className="flex items-center gap-2">
          <span className="h-3 w-3 rounded-full shrink-0" style={{ background: entry.color }} />
          <span className="flex-1 font-semibold">{index + 1}. {entry.settings.algorithm}</span>
          <button
            aria-label={`${entry.visible ? "Hide" : "Show"} ${index + 1}. ${entry.settings.algorithm}`}
            aria-pressed={!entry.visible}
            onClick={() => onToggleVisibility(entry.id)}
          >
            {entry.visible ? "Hide" : "Show"}
          </button>
          <button aria-label={`Remove ${index + 1}. ${entry.settings.algorithm}`} onClick={() => onRemove(entry.id)}>Remove</button>
        </div>
        <div className="text-xs text-stone-600">{Object.entries(entry.settings).filter(([key]) => !["mode", "functionExpr", "algorithm"].includes(key) && (key !== "y0" || settings.mode === "Bivariate")).map(([key, value]) => `${key}: ${value}`).join(", ")}</div>
        {trajectories.find(item => item.id === entry.id)?.error && <p role="alert" className="text-red-700">Unable to compute this trajectory. Check its settings.</p>}
      </li>)}
    </ul>
    <div className="text-sm" role="status">Iteration {iteration}{busy ? " · Computing…" : ""}</div>
    <div className="flex flex-wrap gap-2">
      {["Animate", "Previous", "Next", "Reset", "Pause"].map(action => <button key={action}
        className="rounded border border-gray-300 bg-orange-200 px-3 py-1 text-sm disabled:opacity-50"
        disabled={action === "Pause" ? !animating : !ready || !entries.length || busy || animating || ((action === "Previous" || action === "Reset") && iteration === 0)}
        onClick={() => onAction(action)}>{action}</button>)}
    </div>
  </>;
}