Merge branch 'main' into artifact

This commit is contained in:
c-bata
2023-01-26 11:54:32 +09:00
9 changed files with 3485 additions and 2028 deletions
+40
View File
@@ -25,6 +25,7 @@ from optuna.storages import RDBStorage
from optuna.study import StudyDirection
from optuna.study import StudySummary
from optuna.trial import FrozenTrial
from optuna.trial import TrialState
from optuna.version import __version__ as optuna_ver
from packaging import version
@@ -405,6 +406,45 @@ def create_app(
response.status = 204 # No content
return {}
@app.post("/api/trials/<trial_id:int>/tell")
@json_api_view
def tell_trial(trial_id: int) -> BottleViewReturn:
if "state" not in request.json:
response.status = 400 # Bad request
return {"reason": "state must be specified."}
try:
state = TrialState[request.json["state"].upper()]
except Exception: # To catch KeyError and Exception by non str case.
response.status = 400 # Bad request
return {"reason": "state must be either 'Complete' or 'Fail'."}
if state not in [TrialState.COMPLETE, TrialState.FAIL]:
response.status = 400 # Bad request
return {"reason": "state must be either 'Complete' or 'Fail'."}
values = None
if state == TrialState.COMPLETE:
vs = request.json.get("values")
if vs is None:
response.status = 400 # Bad request
return {"reason": "values attribute is required when state is 'Complete'."}
try:
values = [float(v) for v in vs]
except (ValueError, TypeError):
response.status = 400 # Bad request
return {"reason": "values attribute must be an array of numbers"}
try:
storage.set_trial_state_values(trial_id, state, values)
except Exception as e:
response.status = 500
return {"reason": f"Internal server error: {e}"}
response.status = 204
return {}
@app.put("/api/studies/<study_id:int>/<trial_id:int>/note")
@json_api_view
def save_trial_note(study_id: int, trial_id: int) -> dict[str, Any]:
+1 -1
View File
@@ -86,7 +86,7 @@ def get_param_importance_from_trials_cache(
) -> list[ImportanceType]:
completed_trials = [t for t in trials if t.state == TrialState.COMPLETE]
n_completed_trials = len(completed_trials)
if n_completed_trials == 0:
if n_completed_trials <= 1:
return []
cache_key = f"{study_id}:{objective_id}"
+59
View File
@@ -8,6 +8,7 @@ import {
deleteStudyAPI,
saveStudyNoteAPI,
saveTrialNoteAPI,
tellTrialAPI,
renameStudyAPI,
uploadArtifactAPI,
getMetaInfoAPI,
@@ -95,6 +96,25 @@ export const actionCreator = () => {
setTrialArtifacts(studyId, index, newArtifacts)
}
const setTrialStateValues = (
studyId: number,
index: number,
state: TrialState,
values?: TrialValueNumber[]
) => {
const newTrial: Trial = Object.assign(
{},
studyDetails[studyId].trials[index]
)
newTrial.state = state
newTrial.values = values
const newTrials: Trial[] = [...studyDetails[studyId].trials]
newTrials[index] = newTrial
const newStudy: StudyDetail = Object.assign({}, studyDetails[studyId])
newStudy.trials = newTrials
setStudyDetailState(studyId, newStudy)
}
const setStudyParamImportanceState = (
studyId: number,
importance: ParamImportance[][]
@@ -369,6 +389,44 @@ export const actionCreator = () => {
})
}
const tellTrial = (
studyId: number,
trialId: number,
state: TrialStateFinished,
values?: number[]
): Promise<void> => {
const message =
values === undefined
? `id=${trialId}, state=${state}`
: `id=${trialId}, state=${state}, values=${values}`
return tellTrialAPI(trialId, state, values)
.then(() => {
const index = studyDetails[studyId].trials.findIndex(
(t) => t.trial_id === trialId
)
if (index === -1) {
enqueueSnackbar(`Unexpected error happens. Please reload the page.`, {
variant: "error",
})
return
}
setTrialStateValues(studyId, index, state, values)
enqueueSnackbar(`Successfully updated trial (${message})`, {
variant: "success",
})
})
.catch((err) => {
const reason = err.response?.data.reason
enqueueSnackbar(
`Failed to update trial (${message}). Reason: ${reason}`,
{
variant: "error",
}
)
console.log(err)
})
}
return {
updateAPIMeta,
updateStudyDetail,
@@ -383,6 +441,7 @@ export const actionCreator = () => {
saveTrialNote,
uploadArtifact,
deleteArtifact,
tellTrial,
}
}
+17
View File
@@ -262,6 +262,23 @@ export const deleteArtifactAPI = (
})
}
export const tellTrialAPI = (
trialId: number,
state: TrialStateFinished,
values?: number[]
): Promise<void> => {
const req: { state: TrialState; values?: number[] } = {
state: state,
values: values,
}
return axiosInstance
.post<void>(`/api/trials/${trialId}/tell`, req)
.then((res) => {
return
})
}
interface ParamImportancesResponse {
param_importances: ParamImportance[][]
}
+95 -2
View File
@@ -1,10 +1,20 @@
import React, { FC } from "react"
import { Typography, Grid, Box, IconButton } from "@mui/material"
import React, { createRef, FC, FormEvent, MouseEvent } from "react"
import {
Typography,
Grid,
Box,
Button,
IconButton,
Stack,
TextField,
} from "@mui/material"
import LinkIcon from "@mui/icons-material/Link"
import { DataGridColumn, DataGrid } from "./DataGrid"
import { Link } from "react-router-dom"
import { actionCreator } from "../action"
export const TrialTable: FC<{
studyDetail: StudyDetail | null
isBeta: boolean
@@ -12,6 +22,7 @@ export const TrialTable: FC<{
}> = ({ studyDetail, isBeta, initialRowsPerPage }) => {
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
const objectiveNames: string[] = studyDetail?.objective_names || []
const action = actionCreator()
const columns: DataGridColumn<Trial>[] = [
{ field: "number", label: "Number", sortable: true, padding: "none" },
@@ -263,6 +274,39 @@ export const TrialTable: FC<{
]
const collapseBody = (index: number) => {
const objectiveFormRefs = studyDetail?.directions.map((d) =>
createRef<HTMLInputElement>()
)
const handleSubmit = (e: FormEvent<HTMLFormElement>): void => {
if (objectiveFormRefs === undefined) {
return
}
if (studyDetail === null) {
return
}
e.preventDefault()
const studyId = studyDetail.id
const trialId = trials[index].trial_id
const objectiveValues = objectiveFormRefs.map((ref) =>
ref.current ? Number(ref.current.value) : NaN
)
if (objectiveValues.includes(NaN)) {
return
}
action.tellTrial(studyId, trialId, "Complete", objectiveValues)
}
const handleFailTrial = (e: MouseEvent<HTMLButtonElement>): void => {
if (studyDetail === null) {
return
}
const studyId = studyDetail.id
const trialId = trials[index].trial_id
action.tellTrial(studyId, trialId, "Fail")
}
return (
<Grid container direction="row">
<Grid item xs={6}>
@@ -293,6 +337,55 @@ export const TrialTable: FC<{
/>
</Box>
</Grid>
{trials[index].state === "Running" ? (
<Grid item xs={12}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Trial tell
</Typography>
<form onSubmit={handleSubmit}>
<Box margin={1}>
<Stack direction="row" spacing={1}>
{objectiveFormRefs !== undefined &&
objectiveFormRefs.map((ref, i) => (
<TextField
required
id={`objective-${i}`}
key={`objective-${i}`}
label={
objectiveNames.length ===
studyDetail?.directions.length
? objectiveNames[i]
: `Objective ${i}`
}
inputProps={{
inputMode: "numeric",
pattern: "[-+]?[0-9]*.?[0-9]+([eE][-+]?[0-9]+)?",
title: "Please input a float number",
}}
inputRef={ref}
/>
))}
</Stack>
</Box>
<Box margin={1}>
<Stack direction="row" spacing={1}>
<Button variant="contained" type="submit">
Submit
</Button>
<Button
variant="outlined"
color="error"
onClick={handleFailTrial}
>
Fail Trial
</Button>
</Stack>
</Box>
</form>
</Box>
</Grid>
) : null}
</Grid>
)
}
+1
View File
@@ -10,6 +10,7 @@ declare const URL_PREFIX: string
type TrialValueNumber = number | "inf" | "-inf"
type TrialIntermediateValueNumber = number | "inf" | "-inf" | "nan"
type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting"
type TrialStateFinished = "Complete" | "Fail" | "Pruned"
type StudyDirection = "maximize" | "minimize" | "not_set"
type FloatDistribution = {
+3241 -2017
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -38,7 +38,7 @@
"@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.5",
"@testing-library/react": "^13.4.0",
"@types/jest": "^27.4.1",
"@types/jest": "^29.2.1",
"@types/plotly.js": "^2.12.11",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.10",
@@ -49,10 +49,11 @@
"compression-webpack-plugin": "^10.0.0",
"esbuild-loader": "^2.18.0",
"eslint": "^7.28.0",
"jest": "^27.4.1",
"jest": "^29.2.1",
"jest-canvas-mock": "^2.3.1",
"jest-environment-jsdom": "^29.3.1",
"prettier": "^2.5.1",
"ts-jest": "^27.1.3",
"ts-jest": "^29.0.3",
"ts-loader": "^9.2.7",
"typescript": "^4.6.2",
"webpack": "^5.70.0",
+27 -5
View File
@@ -1,6 +1,8 @@
import React from "react"
global.URL.createObjectURL = jest.fn()
import { SnackbarProvider } from "notistack"
import { RecoilRoot } from "recoil"
import { cleanup, render, within, fireEvent } from "@testing-library/react"
import { TrialTable } from "../optuna_dashboard/ts/components/TrialTable"
@@ -126,7 +128,11 @@ const studyDetail: StudyDetail = {
it("Sort TrialTable by trial number", () => {
const { getAllByRole, getByText } = render(
<TrialTable studyDetail={studyDetail} isBeta={false} />
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
const rows = getAllByRole("row")
@@ -142,7 +148,11 @@ it("Sort TrialTable by trial number", () => {
it("Sort TrialTable by value", () => {
const { getAllByRole, getByText } = render(
<TrialTable studyDetail={studyDetail} isBeta={false} />
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
fireEvent.click(getByText("Value"))
const rows = getAllByRole("row")
@@ -157,7 +167,11 @@ it("Sort TrialTable by value", () => {
it("Sort TrialTable by duration", () => {
const { getAllByRole, getByText } = render(
<TrialTable studyDetail={studyDetail} isBeta={false} />
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
fireEvent.click(getByText("Duration(ms)"))
const rows = getAllByRole("row")
@@ -172,7 +186,11 @@ it("Sort TrialTable by duration", () => {
it("Sort TrialTable by state", () => {
const { getAllByRole, getByText } = render(
<TrialTable studyDetail={studyDetail} isBeta={false} />
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
fireEvent.click(getByText("State"))
const rows = getAllByRole("row")
@@ -187,7 +205,11 @@ it("Sort TrialTable by state", () => {
it("Filter trials by state", () => {
const { queryAllByText } = render(
<TrialTable studyDetail={studyDetail} isBeta={false} />
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
expect(queryAllByText("Fail").length).toBe(1)