diff --git a/.github/workflows/typescript-tests.yml b/.github/workflows/typescript-tests.yml index 26c98501..88f186c5 100644 --- a/.github/workflows/typescript-tests.yml +++ b/.github/workflows/typescript-tests.yml @@ -109,9 +109,27 @@ jobs: with: node-version: '20' + - name: Build rustlib for standalone_app + working-directory: rustlib + run: | + curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + wasm-pack build --target web + - name: Setup tslib run: make tslib + - name: Build standalone_app for vscode extension + working-directory: standalone_app + run: | + npm install + npm run build:vscode + + - name: Build standalone_app for GitHub pages + working-directory: standalone_app + run: | + npm install + npx vite build --out-dir ./gh-pages + - name: Build bundle.js working-directory: optuna_dashboard run: | diff --git a/optuna_dashboard/artifact/_backend.py b/optuna_dashboard/artifact/_backend.py index 6ea65987..3be9f4c8 100644 --- a/optuna_dashboard/artifact/_backend.py +++ b/optuna_dashboard/artifact/_backend.py @@ -74,7 +74,7 @@ def get_artifact_path( def register_artifact_route( - app: Bottle, storage: BaseStorage, artifact_store: Optional[ArtifactStore] + app: Bottle, storage: BaseStorage, artifact_store: ArtifactStore | None ) -> None: @app.get("/artifacts//") def proxy_study_artifact(study_id: int, artifact_id: str) -> HTTPResponse | bytes: @@ -243,8 +243,8 @@ def upload_artifact( trial: optuna.Trial, file_path: str, *, - mimetype: Optional[str] = None, - encoding: Optional[str] = None, + mimetype: str | None = None, + encoding: str | None = None, ) -> str: """Upload an artifact (files), which is associated with the trial. @@ -300,7 +300,7 @@ def _dashboard_artifact_prefix(trial_id: int) -> str: def get_study_artifact_meta( storage: BaseStorage, study_id: int, artifact_id: str -) -> Optional[ArtifactMeta]: +) -> ArtifactMeta | None: study_system_attrs = storage.get_study_system_attrs(study_id) attr_key = ARTIFACTS_ATTR_PREFIX + artifact_id artifact_meta = study_system_attrs.get(attr_key) @@ -311,7 +311,7 @@ def get_study_artifact_meta( def get_trial_artifact_meta( storage: BaseStorage, study_id: int, trial_id: int, artifact_id: str -) -> Optional[ArtifactMeta]: +) -> ArtifactMeta | None: # Search study_system_attrs due to backward compatibility. study_system_attrs = storage.get_study_system_attrs(study_id) attr_key = _dashboard_artifact_prefix(trial_id=trial_id) + artifact_id diff --git a/optuna_dashboard/artifact/boto3.py b/optuna_dashboard/artifact/boto3.py index 46edf165..a0dafd3d 100644 --- a/optuna_dashboard/artifact/boto3.py +++ b/optuna_dashboard/artifact/boto3.py @@ -12,7 +12,6 @@ from optuna_dashboard.artifact.exceptions import ArtifactNotFound if TYPE_CHECKING: from typing import BinaryIO - from typing import Optional from mypy_boto3_s3 import S3Client @@ -43,7 +42,7 @@ class Boto3Backend: """ def __init__( - self, bucket_name: str, client: Optional[S3Client] = None, *, avoid_buf_copy: bool = False + self, bucket_name: str, client: S3Client | None = None, *, avoid_buf_copy: bool = False ) -> None: self.bucket = bucket_name self.client = client or boto3.client("s3") diff --git a/optuna_dashboard/artifact/exceptions.py b/optuna_dashboard/artifact/exceptions.py index abc014df..78913f26 100644 --- a/optuna_dashboard/artifact/exceptions.py +++ b/optuna_dashboard/artifact/exceptions.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class ArtifactNotFound(Exception): """Exception raised when an artifact is not found. diff --git a/optuna_dashboard/package-lock.json b/optuna_dashboard/package-lock.json index 2812e249..67a6b4e8 100644 --- a/optuna_dashboard/package-lock.json +++ b/optuna_dashboard/package-lock.json @@ -73,14 +73,13 @@ "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", - "@optuna/storage": "file:../storage", "@tanstack/react-table": "^8.17.3", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", - "react-dom": "^18.2.0", - "recoil": "^0.7.7" + "react-dom": "^18.2.0" }, "devDependencies": { + "@optuna/storage": "file:../storage", "@optuna/types": "file:../types", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", @@ -7186,10 +7185,6 @@ "gunzip-maybe": "bin.js" } }, - "../tslib/react/node_modules/hamt_plus": { - "version": "1.0.2", - "license": "MIT" - }, "../tslib/react/node_modules/handlebars": { "version": "4.7.8", "dev": true, @@ -9944,24 +9939,6 @@ "dev": true, "license": "0BSD" }, - "../tslib/react/node_modules/recoil": { - "version": "0.7.7", - "license": "MIT", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "../tslib/react/node_modules/redent": { "version": "3.0.0", "dev": true, @@ -12089,6 +12066,7 @@ "../tslib/storage": { "name": "@optuna/storage", "version": "0.0.1", + "dev": true, "license": "MIT", "dependencies": { "@sqlite.org/sqlite-wasm": "^3.45.1-build1" @@ -12104,6 +12082,7 @@ }, "../tslib/storage/node_modules/@sqlite.org/sqlite-wasm": { "version": "3.45.1-build1", + "dev": true, "license": "Apache-2.0", "bin": { "sqlite-wasm": "bin/index.js" diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 90f6b22c..0afd5869 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -5,7 +5,6 @@ import { FeedbackComponentType, FormWidgets, Note, - ParamImportance, PlotlyGraphObject, PreferenceFeedbackMode, PreferenceHistory, @@ -113,7 +112,7 @@ export type UploadArtifactAPIResponse = { } export interface ParamImportancesResponse { - param_importances: ParamImportance[][] + param_importances: Optuna.ParamImportance[][] } export type PlotResponse = { @@ -228,7 +227,9 @@ export abstract class APIClient { trialId: number, user_attrs: { [key: string]: number | string } ): Promise - abstract getParamImportances(studyId: number): Promise + abstract getParamImportances( + studyId: number + ): Promise abstract reportPreference( studyId: number, candidates: number[], diff --git a/optuna_dashboard/ts/axiosClient.ts b/optuna_dashboard/ts/axiosClient.ts index af2eda97..dd68d779 100644 --- a/optuna_dashboard/ts/axiosClient.ts +++ b/optuna_dashboard/ts/axiosClient.ts @@ -15,7 +15,6 @@ import { } from "./apiClient" import { FeedbackComponentType, - ParamImportance, StudyDetail, StudySummary, Trial, @@ -230,7 +229,9 @@ export class AxiosClient extends APIClient { .then(() => { return }) - getParamImportances = (studyId: number): Promise => + getParamImportances = ( + studyId: number + ): Promise => this.axiosInstance .get( `/api/studies/${studyId}/param_importances` diff --git a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx index 7ea83290..c242fdb3 100644 --- a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx +++ b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx @@ -67,5 +67,5 @@ export const ArtifactCardMedia: FC<{ /> ) } - return + return } diff --git a/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx index 9fd12cf4..b24995f2 100644 --- a/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx +++ b/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx @@ -41,6 +41,15 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => { const width = "200px" const height = "150px" + const artifacts = [...study.artifacts].sort((a, b) => { + if (a.filename < b.filename) { + return -1 + } else if (a.filename > b.filename) { + return 1 + } else { + return 0 + } + }) return ( <> @@ -48,7 +57,7 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => { component="div" sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }} > - {study.artifacts.map((artifact) => { + {artifacts.map((artifact) => { const urlPath = `/artifacts/${study.id}/${artifact.artifact_id}` return ( = ( }) const keys = Array.from(unionSet) return keys.map((key) => ({ - header: key, + // ``header`` cannot be a falsy value, so replace key with a string looking like an empty string. + header: key || " ", accessorFn: (info: Data) => typeof info[key] === "object" ? JSON.stringify(info[key]) : info[key], enableSorting: true, @@ -142,6 +143,7 @@ const loadCSV = (props: TableArtifactViewerProps): Promise => { Papa.parse(props.src, { header: true, download: true, + skipEmptyLines: true, complete: (results: Papa.ParseResult) => { resolve(results?.data) }, diff --git a/optuna_dashboard/ts/components/Artifact/TrialArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/TrialArtifactCards.tsx index fe7593ce..7bd29c30 100644 --- a/optuna_dashboard/ts/components/Artifact/TrialArtifactCards.tsx +++ b/optuna_dashboard/ts/components/Artifact/TrialArtifactCards.tsx @@ -24,6 +24,7 @@ import { Trial } from "ts/types/optuna" import { actionCreator } from "../../action" import { ArtifactCardMedia } from "./ArtifactCardMedia" import { useDeleteTrialArtifactDialog } from "./DeleteArtifactDialog" +import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer" import { isThreejsArtifact, useThreejsArtifactModal, @@ -35,12 +36,23 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => { useDeleteTrialArtifactDialog() const [openThreejsArtifactModal, renderThreejsArtifactModal] = useThreejsArtifactModal() + const [openTableArtifactModal, renderTableArtifactModal] = + useTableArtifactModal() const isArtifactModifiable = (trial: Trial) => { return trial.state === "Running" || trial.state === "Waiting" } const width = "200px" const height = "150px" + const artifacts = [...trial.artifacts].sort((a, b) => { + if (a.filename < b.filename) { + return -1 + } else if (a.filename > b.filename) { + return 1 + } else { + return 0 + } + }) return ( <> @@ -54,7 +66,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => { component="div" sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }} > - {trial.artifacts.map((artifact) => { + {artifacts.map((artifact) => { const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}` return ( = ({ trial }) => { marginBottom: theme.spacing(2), width: width, margin: theme.spacing(0, 1, 1, 0), + display: "flex", + flexDirection: "column", + alignItems: "center", }} > = ({ trial }) => { sx={{ p: theme.spacing(0.5, 0), flexGrow: 1, - wordWrap: "break-word", + wordBreak: "break-all", maxWidth: `calc(100% - ${theme.spacing( 4 + (isThreejsArtifact(artifact) ? 4 : 0) + @@ -104,6 +119,19 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => { ) : null} + {isTableArtifact(artifact) ? ( + { + openTableArtifactModal(urlPath, artifact) + }} + > + + + ) : null} {isArtifactModifiable(trial) ? ( = ({ trial }) => { {renderDeleteArtifactDialog()} {renderThreejsArtifactModal()} + {renderTableArtifactModal()} ) } diff --git a/optuna_dashboard/ts/components/GraphEdf.tsx b/optuna_dashboard/ts/components/GraphEdf.tsx index 234b2e6f..d067f8f3 100644 --- a/optuna_dashboard/ts/components/GraphEdf.tsx +++ b/optuna_dashboard/ts/components/GraphEdf.tsx @@ -1,10 +1,11 @@ +import { useTheme } from "@mui/material" import { GraphContainer, PlotEdf, useGraphComponentState } from "@optuna/react" import * as plotly from "plotly.js-dist-min" import React, { FC, useEffect } from "react" import { StudyDetail } from "ts/types/optuna" import { CompareStudiesPlotType } from "../apiClient" import { useAPIClient } from "../apiClientProvider" -import { useBackendRender } from "../state" +import { useBackendRender, usePlotlyColorTheme } from "../state" export const GraphEdf: FC<{ studies: StudyDetail[] @@ -13,7 +14,15 @@ export const GraphEdf: FC<{ if (useBackendRender()) { return } else { - return + const theme = useTheme() + const colorTheme = usePlotlyColorTheme(theme.palette.mode) + return ( + + ) } } diff --git a/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx b/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx index 33f6a4ea..6aadbba1 100644 --- a/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx +++ b/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx @@ -1,16 +1,13 @@ -import { Box, Card, CardContent, Typography, useTheme } from "@mui/material" +import { Box, Card, CardContent } from "@mui/material" import * as plotly from "plotly.js-dist-min" import React, { FC, useEffect } from "react" -import { ParamImportance, StudyDetail } from "ts/types/optuna" +import { PlotImportance } from "@optuna/react" +import { StudyDetail } from "ts/types/optuna" import { PlotType } from "../apiClient" import { useParamImportance } from "../hooks/useParamImportance" import { usePlot } from "../hooks/usePlot" -import { - useBackendRender, - usePlotlyColorTheme, - useStudyDirections, -} from "../state" +import { useBackendRender } from "../state" const plotDomId = "graph-hyperparameter-importances" @@ -19,6 +16,13 @@ export const GraphHyperparameterImportance: FC<{ study: StudyDetail | null graphHeight: string }> = ({ studyId, study = null, graphHeight }) => { + const numCompletedTrials = + study?.trials.filter((t) => t.state === "Complete").length || 0 + const { importances } = useParamImportance({ + numCompletedTrials, + studyId, + }) + if (useBackendRender()) { return ( + + + + + ) } } @@ -64,100 +72,3 @@ const GraphHyperparameterImportanceBackend: FC<{ return } - -const GraphHyperparameterImportanceFrontend: FC<{ - studyId: number - study: StudyDetail | null - graphHeight: string -}> = ({ studyId, study = null, graphHeight }) => { - const theme = useTheme() - const colorTheme = usePlotlyColorTheme(theme.palette.mode) - - const numCompletedTrials = - study?.trials.filter((t) => t.state === "Complete").length || 0 - const { importances } = useParamImportance({ - numCompletedTrials, - studyId, - }) - const nObjectives = useStudyDirections(studyId)?.length - const objectiveNames: string[] = - study?.objective_names || - study?.directions.map((d, i) => `Objective ${i}`) || - [] - - useEffect(() => { - if (importances !== undefined && nObjectives === importances.length) { - plotParamImportance(importances, objectiveNames, colorTheme) - } - }, [nObjectives, importances, colorTheme]) - - return ( - - - - Hyperparameter Importance - - - - - ) -} - -const plotParamImportance = ( - importances: ParamImportance[][], - objectiveNames: string[], - colorTheme: Partial -) => { - const layout: Partial = { - xaxis: { - title: "Hyperparameter Importance", - }, - yaxis: { - title: "Hyperparameter", - automargin: true, - }, - margin: { - l: 50, - t: 0, - r: 50, - b: 50, - }, - barmode: "group", - bargap: 0.15, - bargroupgap: 0.1, - uirevision: "true", - template: colorTheme, - legend: { - x: 1.0, - y: 0.95, - }, - } - - if (document.getElementById(plotDomId) === null) { - return - } - const traces: Partial[] = importances.map( - (importance, i) => { - const reversed = [...importance].reverse() - const importance_values = reversed.map((p) => p.importance) - const param_names = reversed.map((p) => p.name) - const param_hover_templates = reversed.map( - (p) => `${p.name} (${p.distribution}): ${p.importance} ` - ) - return { - type: "bar", - orientation: "h", - name: objectiveNames[i], - x: importance_values, - y: param_names, - text: importance_values.map((v) => String(v.toFixed(2))), - textposition: "outside", - hovertemplate: param_hover_templates, - } - } - ) - plotly.react(plotDomId, traces, layout) -} diff --git a/optuna_dashboard/ts/components/GraphIntermediateValues.tsx b/optuna_dashboard/ts/components/GraphIntermediateValues.tsx index 3552f64a..eae2c43e 100644 --- a/optuna_dashboard/ts/components/GraphIntermediateValues.tsx +++ b/optuna_dashboard/ts/components/GraphIntermediateValues.tsx @@ -1,102 +1,22 @@ -import { Box, Card, CardContent, Typography, useTheme } from "@mui/material" -import * as plotly from "plotly.js-dist-min" -import React, { FC, useEffect } from "react" +import { Card, CardContent } from "@mui/material" +import { PlotIntermediateValues } from "@optuna/react" +import React, { FC } from "react" import { Trial } from "ts/types/optuna" -import { usePlotlyColorTheme } from "../state" - -const plotDomId = "graph-intermediate-values" export const GraphIntermediateValues: FC<{ trials: Trial[] includePruned: boolean logScale: boolean }> = ({ trials, includePruned, logScale }) => { - const theme = useTheme() - const colorTheme = usePlotlyColorTheme(theme.palette.mode) - - useEffect(() => { - plotIntermediateValue(trials, colorTheme, false, !includePruned, logScale) - }, [trials, colorTheme, includePruned, logScale]) - return ( - - Intermediate values - - + ) } - -const plotIntermediateValue = ( - trials: Trial[], - colorTheme: Partial, - filterCompleteTrial: boolean, - filterPrunedTrial: boolean, - logScale: boolean -) => { - if (document.getElementById(plotDomId) === null) { - return - } - - const layout: Partial = { - margin: { - l: 50, - t: 0, - r: 50, - b: 0, - }, - yaxis: { - title: "Objective Value", - type: logScale ? "log" : "linear", - }, - xaxis: { - title: "Step", - type: "linear", - }, - uirevision: "true", - template: colorTheme, - legend: { - x: 1.0, - y: 0.95, - }, - } - if (trials.length === 0) { - plotly.react(plotDomId, [], layout) - return - } - - const filteredTrials = trials.filter( - (t) => - (!filterCompleteTrial && t.state === "Complete") || - (!filterPrunedTrial && - t.state === "Pruned" && - t.values && - t.values.length > 0) || - t.state === "Running" - ) - const plotData: Partial[] = filteredTrials.map((trial) => { - const isFeasible = trial.constraints.every((c) => c <= 0) - return { - x: trial.intermediate_values.map((iv) => iv.step), - y: trial.intermediate_values.map((iv) => iv.value), - marker: { maxdisplayed: 10 }, - mode: "lines+markers", - type: "scatter", - name: `trial #${trial.number} ${ - trial.state === "Running" - ? "(running)" - : !isFeasible - ? "(infeasible)" - : "" - }`, - ...(!isFeasible && { line: { color: "#CCCCCC" } }), - } - }) - plotly.react(plotDomId, plotData, layout) -} diff --git a/optuna_dashboard/ts/components/GraphTimeline.tsx b/optuna_dashboard/ts/components/GraphTimeline.tsx index 766d4951..fbaff870 100644 --- a/optuna_dashboard/ts/components/GraphTimeline.tsx +++ b/optuna_dashboard/ts/components/GraphTimeline.tsx @@ -157,7 +157,7 @@ const plotTimeline = ( xaxis: { title: "Datetime", type: "date", - range: [minDatetime.toISOString(), maxDatetime.toISOString()], + range: [minDatetime, maxDatetime], }, yaxis: { title: "Trial", @@ -188,7 +188,7 @@ const plotTimeline = ( x: runDurations, y: bars.map((b) => b.number), // @ts-ignore: To suppress ts(2322) - base: starts.map((s) => s.toISOString()), + base: starts, name: state, text: bars.map((b) => makeHovertext(b)), hovertemplate: "%{text}" + state + "", diff --git a/optuna_dashboard/ts/hooks/useParamImportance.ts b/optuna_dashboard/ts/hooks/useParamImportance.ts index 9cddaafa..e5baef6f 100644 --- a/optuna_dashboard/ts/hooks/useParamImportance.ts +++ b/optuna_dashboard/ts/hooks/useParamImportance.ts @@ -1,8 +1,8 @@ +import * as Optuna from "@optuna/types" import { useQuery } from "@tanstack/react-query" import { AxiosError } from "axios" import { useSnackbar } from "notistack" import { useEffect } from "react" -import { ParamImportance } from "ts/types/optuna" import { useAPIClient } from "../apiClientProvider" export const useParamImportance = ({ @@ -13,7 +13,7 @@ export const useParamImportance = ({ const { enqueueSnackbar } = useSnackbar() const { data, isLoading, error } = useQuery< - ParamImportance[][], + Optuna.ParamImportance[][], AxiosError<{ reason: string }> >({ queryKey: ["paramImportance", studyId, numCompletedTrials], diff --git a/optuna_dashboard/ts/types/optuna.ts b/optuna_dashboard/ts/types/optuna.ts index 01844f72..f69d008a 100644 --- a/optuna_dashboard/ts/types/optuna.ts +++ b/optuna_dashboard/ts/types/optuna.ts @@ -21,12 +21,6 @@ export type TrialParam = { distribution: Optuna.Distribution } -export type ParamImportance = { - name: string - importance: number - distribution: Optuna.Distribution -} - export type SearchSpaceItem = { name: string distribution: Optuna.Distribution diff --git a/python_tests/artifact/test_backend.py b/python_tests/artifact/test_backend.py index 56fdf6f5..38333fe9 100644 --- a/python_tests/artifact/test_backend.py +++ b/python_tests/artifact/test_backend.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import base64 import json import tempfile diff --git a/python_tests/artifact/test_backoff.py b/python_tests/artifact/test_backoff.py index 5d99c1b7..cbcdafad 100644 --- a/python_tests/artifact/test_backoff.py +++ b/python_tests/artifact/test_backoff.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import uuid diff --git a/python_tests/artifact/test_boto3.py b/python_tests/artifact/test_boto3.py index 9a48fb3e..be82abfc 100644 --- a/python_tests/artifact/test_boto3.py +++ b/python_tests/artifact/test_boto3.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io from unittest import TestCase diff --git a/python_tests/artifact/test_file_system.py b/python_tests/artifact/test_file_system.py index ceaaf125..07f1a890 100644 --- a/python_tests/artifact/test_file_system.py +++ b/python_tests/artifact/test_file_system.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import tempfile from unittest import TestCase diff --git a/python_tests/artifact/test_prefix.py b/python_tests/artifact/test_prefix.py index 87287d4c..39cdeb7f 100644 --- a/python_tests/artifact/test_prefix.py +++ b/python_tests/artifact/test_prefix.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import uuid diff --git a/python_tests/preferential/samplers/test_gp.py b/python_tests/preferential/samplers/test_gp.py index e5df9727..fba0023a 100644 --- a/python_tests/preferential/samplers/test_gp.py +++ b/python_tests/preferential/samplers/test_gp.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from unittest.mock import patch diff --git a/python_tests/streamlit/test_streamlit_helper.py b/python_tests/streamlit/test_streamlit_helper.py index 8f324820..7745634d 100644 --- a/python_tests/streamlit/test_streamlit_helper.py +++ b/python_tests/streamlit/test_streamlit_helper.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import itertools from typing import Sequence -from typing import Union import optuna from optuna_dashboard import ChoiceWidget @@ -60,7 +61,7 @@ for r in range(len(widget_list) + 1): @pytest.mark.parametrize("widgets", widgets_combinations_for_user_attr) def test_render_user_attr_form_widgets( - widgets: Sequence[Union[ChoiceWidget, SliderWidget, TextInputWidget]], + widgets: Sequence[ChoiceWidget | SliderWidget | TextInputWidget], ) -> None: study = optuna.create_study() register_user_attr_form_widgets(study, widgets) # type: ignore @@ -77,7 +78,7 @@ for r in range(1, len(widget_list) + 1): @pytest.mark.parametrize("widgets", widgets_combinations_for_objective) def test_render_objective_form_widgets( - widgets: Sequence[Union[ChoiceWidget, SliderWidget, TextInputWidget]], + widgets: Sequence[ChoiceWidget | SliderWidget | TextInputWidget], ) -> None: study = optuna.create_study(directions=["maximize"] * len(widgets)) register_objective_form_widgets(study, widgets) # type: ignore diff --git a/python_tests/wsgi_client.py b/python_tests/wsgi_client.py index 84fe8aac..0bf527bf 100644 --- a/python_tests/wsgi_client.py +++ b/python_tests/wsgi_client.py @@ -2,8 +2,6 @@ from __future__ import annotations import io import typing -from typing import Optional -from typing import Union from bottle import Bottle from optuna_dashboard._storage import trials_cache @@ -58,9 +56,9 @@ def send_request( app: Bottle, path: str, method: str, - body: Union[str, bytes] = b"", - queries: Optional[dict[str, str]] = None, - headers: Optional[dict[str, str]] = None, + body: str | bytes = b"", + queries: dict[str, str] | None = None, + headers: dict[str, str] | None = None, content_type: str = "text/plain; charset=utf-8", ) -> tuple[int, list[tuple[str, str]], bytes]: status: str = "" diff --git a/standalone_app/package-lock.json b/standalone_app/package-lock.json index 67c7ef31..9ee8c8d7 100644 --- a/standalone_app/package-lock.json +++ b/standalone_app/package-lock.json @@ -56,14 +56,13 @@ "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", - "@optuna/storage": "file:../storage", "@tanstack/react-table": "^8.17.3", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", - "react-dom": "^18.2.0", - "recoil": "^0.7.7" + "react-dom": "^18.2.0" }, "devDependencies": { + "@optuna/storage": "file:../storage", "@optuna/types": "file:../types", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", diff --git a/standalone_app/src/components/StudyDetail.tsx b/standalone_app/src/components/StudyDetail.tsx index ff9b176c..ec37a2a0 100644 --- a/standalone_app/src/components/StudyDetail.tsx +++ b/standalone_app/src/components/StudyDetail.tsx @@ -180,9 +180,7 @@ export const StudyDetail: FC<{ - {!!study && ( - - )} + diff --git a/standalone_app/webpack.config.js b/standalone_app/webpack.config.js index 4c599a49..ed589617 100644 --- a/standalone_app/webpack.config.js +++ b/standalone_app/webpack.config.js @@ -36,6 +36,12 @@ module.exports = { test: /\.wasm$/, type: "asset/inline", }, + { + test: /\.m?js/, + resolve: { + fullySpecified: false + } + } ] }, resolve: { diff --git a/tslib/react/package-lock.json b/tslib/react/package-lock.json index 1ef5fac4..a0ef388c 100644 --- a/tslib/react/package-lock.json +++ b/tslib/react/package-lock.json @@ -15,14 +15,13 @@ "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", - "@optuna/storage": "file:../storage", "@tanstack/react-table": "^8.17.3", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", - "react-dom": "^18.2.0", - "recoil": "^0.7.7" + "react-dom": "^18.2.0" }, "devDependencies": { + "@optuna/storage": "file:../storage", "@optuna/types": "file:../types", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", @@ -47,6 +46,7 @@ "../storage": { "name": "@optuna/storage", "version": "0.0.1", + "dev": true, "license": "MIT", "dependencies": { "@sqlite.org/sqlite-wasm": "^3.45.1-build1" @@ -8409,11 +8409,6 @@ "gunzip-maybe": "bin.js" } }, - "node_modules/hamt_plus": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", - "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==" - }, "node_modules/handlebars": { "version": "4.7.8", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", @@ -11430,25 +11425,6 @@ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, - "node_modules/recoil": { - "version": "0.7.7", - "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz", - "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==", - "dependencies": { - "hamt_plus": "1.0.2" - }, - "peerDependencies": { - "react": ">=16.13.1" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", diff --git a/tslib/react/package.json b/tslib/react/package.json index cbb072c8..bb920b8c 100644 --- a/tslib/react/package.json +++ b/tslib/react/package.json @@ -32,15 +32,14 @@ "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.10", "@mui/system": "^5.15.9", - "@optuna/storage": "file:../storage", "@tanstack/react-table": "^8.17.3", "plotly.js-dist-min": "^2.30.1", "react": "^18.2.0", - "react-dom": "^18.2.0", - "recoil": "^0.7.7" + "react-dom": "^18.2.0" }, "devDependencies": { "@optuna/types": "file:../types", + "@optuna/storage": "file:../storage", "@storybook/addon-essentials": "^8.0.4", "@storybook/addon-interactions": "^8.0.4", "@storybook/addon-links": "^8.0.4", diff --git a/tslib/react/src/components/PlotEdf.tsx b/tslib/react/src/components/PlotEdf.tsx index 56dce17e..65281dcf 100644 --- a/tslib/react/src/components/PlotEdf.tsx +++ b/tslib/react/src/components/PlotEdf.tsx @@ -17,10 +17,13 @@ const getPlotDomId = (objectiveId: number) => `plot-edf-${objectiveId}` export const PlotEdf: FC<{ studies: Optuna.Study[] objectiveId: number -}> = ({ studies, objectiveId }) => { + colorTheme?: Partial +}> = ({ studies, objectiveId, colorTheme }) => { const { graphComponentState, notifyGraphDidRender } = useGraphComponentState() const theme = useTheme() + const colorThemeUsed = + colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {}) const domId = getPlotDomId(objectiveId) const target = useMemo( @@ -39,11 +42,11 @@ export const PlotEdf: FC<{ // biome-ignore lint/correctness/useExhaustiveDependencies: useEffect(() => { if (graphComponentState !== "componentWillMount") { - plotEdf(edfPlotInfos, target, domId, theme.palette.mode)?.then( + plotEdf(edfPlotInfos, target, domId, colorThemeUsed)?.then( notifyGraphDidRender ) } - }, [studies, target, theme.palette.mode, graphComponentState]) + }, [studies, target, colorThemeUsed, graphComponentState]) return ( @@ -65,14 +68,14 @@ const plotEdf = ( edfPlotInfos: EdfPlotInfo[], target: Target, domId: string, - mode: string + colorTheme: Partial ) => { if (document.getElementById(domId) === null) { return } if (edfPlotInfos.length === 0) { return plotly.react(domId, [], { - template: mode === "dark" ? plotlyDarkTemplate : {}, + template: colorTheme, }) } @@ -90,7 +93,7 @@ const plotEdf = ( r: 50, b: 50, }, - template: mode === "dark" ? plotlyDarkTemplate : {}, + template: colorTheme, legend: { x: 1.0, y: 0.95, diff --git a/tslib/react/src/components/PlotImportance.tsx b/tslib/react/src/components/PlotImportance.tsx index f923e78d..96807d2c 100644 --- a/tslib/react/src/components/PlotImportance.tsx +++ b/tslib/react/src/components/PlotImportance.tsx @@ -7,19 +7,20 @@ import { plotlyDarkTemplate } from "./PlotlyDarkMode" const plotDomId = "graph-hyperparameter-importances" export const PlotImportance: FC<{ - study: Optuna.Study - importance: Optuna.ParamImportance[][] -}> = ({ study, importance }) => { + study: Optuna.Study | null + importance?: Optuna.ParamImportance[][] + graphHeight?: string +}> = ({ study = null, importance, graphHeight = "450px" }) => { const theme = useTheme() - const objectiveNames: string[] = study.directions.map( - (_d, i) => `Objective ${i}` - ) + const objectiveNames: string[] = study + ? study.directions.map((_d, i) => `Objective ${i}`) + : [] useEffect(() => { - if (importance.length > 0) { + if (study !== null && importance !== undefined && importance.length > 0) { plotParamImportancesBeta(importance, objectiveNames, theme.palette.mode) } - }, [objectiveNames, importance, theme.palette.mode]) + }, [study, objectiveNames, importance, theme.palette.mode]) return ( <> @@ -29,7 +30,7 @@ export const PlotImportance: FC<{ > Hyperparameter Importance - + ) } @@ -58,6 +59,10 @@ const plotParamImportancesBeta = ( bargroupgap: 0.1, uirevision: "true", template: mode === "dark" ? plotlyDarkTemplate : {}, + legend: { + x: 1.0, + y: 0.95, + }, } if (document.getElementById(plotDomId) === null) { diff --git a/tslib/react/src/components/PlotIntermediateValues.tsx b/tslib/react/src/components/PlotIntermediateValues.tsx index d91c4506..ea339be7 100644 --- a/tslib/react/src/components/PlotIntermediateValues.tsx +++ b/tslib/react/src/components/PlotIntermediateValues.tsx @@ -64,6 +64,10 @@ const plotIntermediateValue = ( }, uirevision: "true", template: mode === "dark" ? plotlyDarkTemplate : {}, + legend: { + x: 1.0, + y: 0.95, + }, } if (trials.length === 0) { plotly.react(plotDomId, [], layout) @@ -79,23 +83,23 @@ const plotIntermediateValue = ( t.values.length > 0) || t.state === "Running" ) + const plotData: Partial[] = filteredTrials.map((trial) => { - const values = trial.intermediate_values.filter( - (iv) => - iv.value !== Infinity && - iv.value !== -Infinity && - !Number.isNaN(iv.value) - ) + const isFeasible = trial.constraints.every((c) => c <= 0) return { - x: values.map((iv) => iv.step), - y: values.map((iv) => iv.value), + x: trial.intermediate_values.map((iv) => iv.step), + y: trial.intermediate_values.map((iv) => iv.value), marker: { maxdisplayed: 10 }, mode: "lines+markers", type: "scatter", - name: - trial.state !== "Running" - ? `trial #${trial.number}` - : `trial #${trial.number} (running)`, + name: `trial #${trial.number} ${ + trial.state === "Running" + ? "(running)" + : !isFeasible + ? "(infeasible)" + : "" + }`, + ...(!isFeasible && { line: { color: "#CCCCCC" } }), } }) plotly.react(plotDomId, plotData, layout) diff --git a/tslib/react/src/utils/loadStorageFromFile.ts b/tslib/react/test/loadStorageFromFile.ts similarity index 94% rename from tslib/react/src/utils/loadStorageFromFile.ts rename to tslib/react/test/loadStorageFromFile.ts index eac68ac4..abc298b9 100644 --- a/tslib/react/src/utils/loadStorageFromFile.ts +++ b/tslib/react/test/loadStorageFromFile.ts @@ -1,6 +1,7 @@ import { JournalFileStorage, SQLite3Storage } from "@optuna/storage" import * as Optuna from "@optuna/types" -import { SetterOrUpdater } from "recoil" + +type SetterOrUpdater = (valOrUpdater: ((currVal: T) => T) | T) => void const readFile = async (file: File) => { return new Promise((resolve, reject) => { diff --git a/tslib/react/test/setup_studies.ts b/tslib/react/test/setup_studies.ts index e3c77059..905d448d 100644 --- a/tslib/react/test/setup_studies.ts +++ b/tslib/react/test/setup_studies.ts @@ -1,6 +1,6 @@ import fs from "node:fs" import * as Optuna from "@optuna/types" -import { loadStorageFromFile } from "../src/utils/loadStorageFromFile" +import { loadStorageFromFile } from "./loadStorageFromFile" declare global { interface Window {