optimization / frontends /react /src /Sidebar.tsx
joel-woodfield's picture
Refactor axis ranges to avoid re-rendering
e048104
raw
history blame
7.62 kB
import { useState } from "react";
import InputField from "./ui/InputField.tsx";
import Tabs from "./ui/Tabs.tsx";
import Dropdown from "./ui/Dropdown.tsx";
import Button from "./ui/Button.tsx";
import Radio from "./ui/Radio.tsx";
import { SUPPORTED_MODES, SUPPORTED_ALGORITHMS, type SettingsUi } from "./types.ts";
const DEFAULT_HYPERPARAMETERS = {
"Gradient Descent": { learningRate: "0.1", momentum: "0.0" },
"Nesterov": { learningRate: "0.1", momentum: "0.0" },
"Adam": { learningRate: "0.1", beta1: "0.9", beta2: "0.999", epsilon: "1e-8" },
"Adagrad": { learningRate: "0.1", epsilon: "1e-8" },
"RMSProp": { learningRate: "0.1", beta: "0.9", epsilon: "1e-8" },
"Adadelta": { beta: "0.9", epsilon: "1e-8" },
"Newton": {}
}
const UNIVARIATE_FUNCTION_OPTIONS = {
"--Custom--": "",
"Quadratic": "x^2",
"Cubic": "x^3 - 3*x^2 + 2*x",
"Quartic": "x^4 - 4*x^3 + 6*x^2 - 4*x + 1",
"Sine": "sin(x)",
"Exponential": "exp(x) - 5",
}
const BIVARIATE_FUNCTION_OPTIONS = {
"--Custom--": "",
"Quadratic": "x^2 + y^2",
"Ackley": "-20 * exp(-0.2 * sqrt(0.5 * (x^2 + y^2))) - exp(0.5 * (cos(2 * pi * x) + cos(2 * pi * y))) + e + 20",
"Rasteringin": "20 + (x^2 - 10 * cos(2 * pi * x)) + (y^2 - 10 * cos(2 * pi * y))",
"Rosenbrock": "(1 - x)^2 + 100 * (y - x^2)^2",
}
interface SidebarProps {
settings: SettingsUi,
setSettings: (settings: SettingsUi) => void,
onReset?: () => void,
onNextStep?: () => void,
onPrevStep?: () => void,
onPlay?: () => void,
onPause?: () => void,
}
export default function Sidebar({
settings,
setSettings,
onReset,
onNextStep,
onPrevStep,
// onPlay,
// onPause,
}: SidebarProps) {
const tabs = ["Settings", "Optimize"] as const;
const [activeTab, setActiveTab] = useState<(typeof tabs)[number]>("Settings");
function updateSettings(key: keyof SettingsUi, value: string) {
if (key === "algorithm") {
const defaults = DEFAULT_HYPERPARAMETERS[value as keyof typeof DEFAULT_HYPERPARAMETERS];
setSettings({ ...settings, algorithm: value as SettingsUi["algorithm"], ...defaults });
} else {
setSettings({ ...settings, [key]: value });
}
}
const [functionOption, setFunctionOption] = useState<string>("Quadratic");
function handleFunctionOptionChange(option: string) {
setFunctionOption(option);
const expr = settings.mode === "Bivariate" ? BIVARIATE_FUNCTION_OPTIONS[option as keyof typeof BIVARIATE_FUNCTION_OPTIONS] : UNIVARIATE_FUNCTION_OPTIONS[option as keyof typeof UNIVARIATE_FUNCTION_OPTIONS];
updateSettings("functionExpr", expr);
}
function handleModeChange(mode: SettingsUi["mode"]) {
// When changing modes, reset function to Quadratic as some options are mode-specific
const newFunctionOption = "Quadratic";
const expr = mode === "Bivariate"
? BIVARIATE_FUNCTION_OPTIONS[newFunctionOption as keyof typeof BIVARIATE_FUNCTION_OPTIONS]
: UNIVARIATE_FUNCTION_OPTIONS[newFunctionOption as keyof typeof UNIVARIATE_FUNCTION_OPTIONS];
// should update mode and functionExpr together as one may override the other
setSettings({ ...settings, mode, functionExpr: expr });
setFunctionOption(newFunctionOption);
}
return (
<div className="bg-gray-100 flex flex-col h-full p-4 gap-2">
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
{/* Tab content */}
{activeTab === "Settings" && (
<>
<Radio
label="Problem Type"
options={SUPPORTED_MODES}
activeOption={settings.mode}
onChange={handleModeChange}
/>
<Dropdown
label="Function"
options={
settings.mode === "Bivariate"
? Object.keys(BIVARIATE_FUNCTION_OPTIONS)
: Object.keys(UNIVARIATE_FUNCTION_OPTIONS)
}
activeOption={functionOption}
onChange={handleFunctionOptionChange}
/>
{functionOption === "--Custom--" && (
<InputField
label="Function Expression"
value={settings.functionExpr}
onChange={(value) => updateSettings("functionExpr", value)}
/>
)}
<Dropdown
label="Algorithm"
options={SUPPORTED_ALGORITHMS}
activeOption={settings.algorithm}
onChange={(value: SettingsUi["algorithm"]) => updateSettings("algorithm", value)}
/>
<div className={`${settings.mode === "Bivariate" ? "grid grid-cols-2 gap-2" : ""}`}>
<InputField
label="Initial X"
value={settings.x0}
onChange={(value) => updateSettings("x0", value)}
/>
{settings.mode === "Bivariate" && (
<InputField
label="Initial Y"
value={settings.y0 || ""}
onChange={(value) => updateSettings("y0", value)}
/>
)}
</div>
{/* todo button for random init */}
<div className="grid grid-cols-2 gap-2">
{["Gradient Descent", "Nesterov", "Adam", "Adagrad", "RMSProp", "Adadelta"].includes(settings.algorithm) && (
<>
<InputField
label="Learning Rate"
value={settings.learningRate}
onChange={(value) => updateSettings("learningRate", value)}
/>
</>
)}
{["Gradient Descent", "Nesterov"].includes(settings.algorithm) && (
<>
<InputField
label="Momentum"
value={settings.momentum}
onChange={(value) => updateSettings("momentum", value)}
/>
</>
)}
{settings.algorithm === "Adam" && (
<>
<InputField
label="Beta 1"
value={settings.beta1}
onChange={(value) => updateSettings("beta1", value)}
/>
<InputField
label="Beta 2"
value={settings.beta2}
onChange={(value) => updateSettings("beta2", value)}
/>
</>
)}
{["RMSProp", "Adadelta"].includes(settings.algorithm) && (
<>
<InputField
label="Beta"
value={settings.beta}
onChange={(value) => updateSettings("beta", value)}
/>
</>
)}
{["Adam", "Adagrad", "RMSProp", "Adadelta"].includes(settings.algorithm) && (
<>
<InputField
label="Epsilon"
value={settings.epsilon}
onChange={(value) => updateSettings("epsilon", value)}
/>
</>
)}
</div>
</>
)}
{activeTab === "Optimize" && (
<>
<div className="grid grid-cols-2 gap-2">
<Button label="Next Step" onClick={onNextStep}/ >
<Button label="Previous Step" onClick={onPrevStep} />
</div>
<Button label="Reset" onClick={onReset}/>
{/* <div className="grid grid-cols-2 gap-2">
<Button label="Play" onClick={onPlay} />
<InputField label="Steps per second" />
</div>
<div className="grid grid-cols-2 gap-2">
<Button label="Pause" onClick={onPause} />
</div> */}
</>
)}
</div>
);
}