Merge branch 'main' into feat/jupyter-lab-optuna

This commit is contained in:
c-bata
2024-08-07 22:34:33 +09:00
35 changed files with 201 additions and 324 deletions
+18
View File
@@ -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: |
+5 -5
View File
@@ -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/<study_id:int>/<artifact_id:re:[0-9a-fA-F-]+>")
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
+1 -2
View File
@@ -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")
+3
View File
@@ -1,3 +1,6 @@
from __future__ import annotations
class ArtifactNotFound(Exception):
"""Exception raised when an artifact is not found.
+4 -25
View File
@@ -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"
+4 -3
View File
@@ -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<void>
abstract getParamImportances(studyId: number): Promise<ParamImportance[][]>
abstract getParamImportances(
studyId: number
): Promise<Optuna.ParamImportance[][]>
abstract reportPreference(
studyId: number,
candidates: number[],
+3 -2
View File
@@ -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<ParamImportance[][]> =>
getParamImportances = (
studyId: number
): Promise<Optuna.ParamImportance[][]> =>
this.axiosInstance
.get<ParamImportancesResponse>(
`/api/studies/${studyId}/param_importances`
@@ -67,5 +67,5 @@ export const ArtifactCardMedia: FC<{
/>
)
}
return <InsertDriveFileIcon sx={{ fontSize: 80 }} />
return <InsertDriveFileIcon sx={{ fontSize: 80, flexGrow: 1 }} />
}
@@ -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 (
<Card
@@ -55,7 +55,8 @@ export const TableArtifactViewer: React.FC<TableArtifactViewerProps> = (
})
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<Data[]> => {
Papa.parse(props.src, {
header: true,
download: true,
skipEmptyLines: true,
complete: (results: Papa.ParseResult<Data>) => {
resolve(results?.data)
},
@@ -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 (
<Card
@@ -63,6 +75,9 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
marginBottom: theme.spacing(2),
width: width,
margin: theme.spacing(0, 1, 1, 0),
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<ArtifactCardMedia
@@ -81,7 +96,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ 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 }) => {
<FullscreenIcon />
</IconButton>
) : null}
{isTableArtifact(artifact) ? (
<IconButton
aria-label="show artifact table"
size="small"
color="inherit"
sx={{ margin: "auto 0" }}
onClick={() => {
openTableArtifactModal(urlPath, artifact)
}}
>
<FullscreenIcon />
</IconButton>
) : null}
{isArtifactModifiable(trial) ? (
<IconButton
aria-label="delete artifact"
@@ -141,6 +169,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
</Box>
{renderDeleteArtifactDialog()}
{renderThreejsArtifactModal()}
{renderTableArtifactModal()}
</>
)
}
+11 -2
View File
@@ -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 <GraphEdfBackend studies={studies} />
} else {
return <PlotEdf studies={studies} objectiveId={objectiveId} />
const theme = useTheme()
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
return (
<PlotEdf
studies={studies}
objectiveId={objectiveId}
colorTheme={colorTheme}
/>
)
}
}
@@ -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 (
<GraphHyperparameterImportanceBackend
@@ -29,11 +33,15 @@ export const GraphHyperparameterImportance: FC<{
)
} else {
return (
<GraphHyperparameterImportanceFrontend
studyId={studyId}
study={study}
graphHeight={graphHeight}
/>
<Card>
<CardContent>
<PlotImportance
study={study}
importance={importances}
graphHeight={graphHeight}
/>
</CardContent>
</Card>
)
}
}
@@ -64,100 +72,3 @@ const GraphHyperparameterImportanceBackend: FC<{
return <Box component="div" id={plotDomId} sx={{ height: graphHeight }} />
}
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 (
<Card>
<CardContent>
<Typography
variant="h6"
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
>
Hyperparameter Importance
</Typography>
<Box component="div" id={plotDomId} sx={{ height: graphHeight }} />
</CardContent>
</Card>
)
}
const plotParamImportance = (
importances: ParamImportance[][],
objectiveNames: string[],
colorTheme: Partial<Plotly.Template>
) => {
const layout: Partial<plotly.Layout> = {
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<plotly.PlotData>[] = 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} <extra></extra>`
)
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)
}
@@ -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 (
<Card>
<CardContent>
<Typography
variant="h6"
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
>
Intermediate values
</Typography>
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
<PlotIntermediateValues
trials={trials}
includePruned={includePruned}
logScale={logScale}
/>
</CardContent>
</Card>
)
}
const plotIntermediateValue = (
trials: Trial[],
colorTheme: Partial<Plotly.Template>,
filterCompleteTrial: boolean,
filterPrunedTrial: boolean,
logScale: boolean
) => {
if (document.getElementById(plotDomId) === null) {
return
}
const layout: Partial<plotly.Layout> = {
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<plotly.PlotData>[] = 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)
}
@@ -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}<extra>" + state + "</extra>",
@@ -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],
-6
View File
@@ -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
+2
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import base64
import json
import tempfile
+2
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import io
import uuid
+2
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import io
from unittest import TestCase
@@ -1,3 +1,5 @@
from __future__ import annotations
import io
import tempfile
from unittest import TestCase
+2
View File
@@ -1,3 +1,5 @@
from __future__ import annotations
import io
import uuid
@@ -1,3 +1,5 @@
from __future__ import annotations
import sys
from unittest.mock import patch
@@ -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
+3 -5
View File
@@ -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 = ""
+2 -3
View File
@@ -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",
@@ -180,9 +180,7 @@ export const StudyDetail: FC<{
<Grid item xs={6}>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
{!!study && (
<PlotImportance study={study} importance={importance} />
)}
<PlotImportance study={study} importance={importance} />
</CardContent>
</Card>
</Grid>
+6
View File
@@ -36,6 +36,12 @@ module.exports = {
test: /\.wasm$/,
type: "asset/inline",
},
{
test: /\.m?js/,
resolve: {
fullySpecified: false
}
}
]
},
resolve: {
+3 -27
View File
@@ -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",
+2 -3
View File
@@ -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",
+9 -6
View File
@@ -17,10 +17,13 @@ const getPlotDomId = (objectiveId: number) => `plot-edf-${objectiveId}`
export const PlotEdf: FC<{
studies: Optuna.Study[]
objectiveId: number
}> = ({ studies, objectiveId }) => {
colorTheme?: Partial<Plotly.Template>
}> = ({ 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<Target>(
@@ -39,11 +42,11 @@ export const PlotEdf: FC<{
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
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 (
<Box component="div">
@@ -65,14 +68,14 @@ const plotEdf = (
edfPlotInfos: EdfPlotInfo[],
target: Target,
domId: string,
mode: string
colorTheme: Partial<Plotly.Template>
) => {
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,
+14 -9
View File
@@ -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
</Typography>
<Box id={plotDomId} sx={{ height: "450px" }} />
<Box id={plotDomId} sx={{ height: graphHeight }} />
</>
)
}
@@ -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) {
@@ -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<plotly.PlotData>[] = 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)
@@ -1,6 +1,7 @@
import { JournalFileStorage, SQLite3Storage } from "@optuna/storage"
import * as Optuna from "@optuna/types"
import { SetterOrUpdater } from "recoil"
type SetterOrUpdater<T> = (valOrUpdater: ((currVal: T) => T) | T) => void
const readFile = async (file: File) => {
return new Promise<ArrayBuffer>((resolve, reject) => {
+1 -1
View File
@@ -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 {