diff --git a/pkg/ui/v1beta1/frontend/src/actions/generalActions.js b/pkg/ui/v1beta1/frontend/src/actions/generalActions.js
index 8407fe238b9..5b6b6af5fb2 100644
--- a/pkg/ui/v1beta1/frontend/src/actions/generalActions.js
+++ b/pkg/ui/v1beta1/frontend/src/actions/generalActions.js
@@ -115,6 +115,35 @@ export const changeStatus = (filter, checked) => ({
checked,
});
+export const CHANGE_EARLY_STOPPING_ALGORITHM = 'CHANGE_EARLY_STOPPING_ALGORITHM';
+
+export const changeEarlyStoppingAlgorithm = algorithmName => ({
+ type: CHANGE_EARLY_STOPPING_ALGORITHM,
+ algorithmName,
+});
+
+export const ADD_EARLY_STOPPING_SETTING = 'ADD_EARLY_STOPPING_SETTING';
+
+export const addEarlyStoppingSetting = () => ({
+ type: ADD_EARLY_STOPPING_SETTING,
+});
+
+export const CHANGE_EARLY_STOPPING_SETTING = 'CHANGE_EARLY_STOPPING_SETTING';
+
+export const changeEarlyStoppingSetting = (index, field, value) => ({
+ type: CHANGE_EARLY_STOPPING_SETTING,
+ index,
+ field,
+ value,
+});
+
+export const DELETE_EARLY_STOPPING_SETTING = 'DELETE_EARLY_STOPPING_SETTING';
+
+export const deleteEarlyStoppingSetting = index => ({
+ type: DELETE_EARLY_STOPPING_SETTING,
+ index,
+});
+
export const CHANGE_TRIAL_TEMPLATE_SOURCE = 'CHANGE_TRIAL_TEMPLATE_SOURCE';
export const changeTrialTemplateSource = source => ({
diff --git a/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/EarlyStopping.jsx b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/EarlyStopping.jsx
new file mode 100644
index 00000000000..43117e16d3e
--- /dev/null
+++ b/pkg/ui/v1beta1/frontend/src/components/Common/Create/Params/EarlyStopping.jsx
@@ -0,0 +1,165 @@
+import React from 'react';
+import { connect } from 'react-redux';
+
+import { makeStyles } from '@material-ui/core/styles';
+import Button from '@material-ui/core/Button';
+import Grid from '@material-ui/core/Grid';
+import Tooltip from '@material-ui/core/Tooltip';
+import HelpOutlineIcon from '@material-ui/icons/HelpOutline';
+import Typography from '@material-ui/core/Typography';
+import MenuItem from '@material-ui/core/MenuItem';
+import FormControl from '@material-ui/core/FormControl';
+import Select from '@material-ui/core/Select';
+import InputLabel from '@material-ui/core/InputLabel';
+import TextField from '@material-ui/core/TextField';
+import IconButton from '@material-ui/core/IconButton';
+import DeleteIcon from '@material-ui/icons/Delete';
+
+import {
+ changeEarlyStoppingAlgorithm,
+ addEarlyStoppingSetting,
+ changeEarlyStoppingSetting,
+ deleteEarlyStoppingSetting,
+} from '../../../../actions/generalActions';
+
+import { GENERAL_MODULE } from '../../../../constants/constants';
+
+const useStyles = makeStyles({
+ textField: {
+ width: '80%',
+ },
+ help: {
+ padding: 4 / 2,
+ verticalAlign: 'middle',
+ marginRight: 5,
+ },
+ parameter: {
+ padding: 2,
+ marginBottom: 10,
+ },
+ icon: {
+ padding: 4,
+ margin: '0 auto',
+ verticalAlign: 'middle !important',
+ },
+ formControl: {
+ width: '100%',
+ },
+ addButton: {
+ margin: 10,
+ },
+});
+
+const EarlyStopping = props => {
+ const classes = useStyles();
+
+ const onEarlyStoppingAlgorithmChange = event => {
+ props.changeEarlyStoppingAlgorithm(event.target.value);
+ };
+
+ const onAddEarlyStoppingSetting = () => {
+ props.addEarlyStoppingSetting();
+ };
+
+ const onChangeEarlyStoppingSetting = (field, index) => event => {
+ props.changeEarlyStoppingSetting(index, field, event.target.value);
+ };
+
+ const onDeleteEarlyStoppingSetting = index => event => {
+ props.deleteEarlyStoppingSetting(index);
+ };
+ return (
+
+
+
+
+
+
+
+
+
+ {'Early Stopping Algorithm Name'}
+
+
+
+
+ Algorithm Name
+
+
+
+
+
+
+ {props.earlyStoppingSettings.map((setting, i) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ );
+};
+
+const mapStateToProps = state => {
+ return {
+ earlyStoppingAlgorithm: state[GENERAL_MODULE].earlyStoppingAlgorithm,
+ allEarlyStoppingAlgorithms: state[GENERAL_MODULE].allEarlyStoppingAlgorithms,
+ earlyStoppingSettings: state[GENERAL_MODULE].earlyStoppingSettings,
+ };
+};
+
+export default connect(mapStateToProps, {
+ changeEarlyStoppingAlgorithm,
+ addEarlyStoppingSetting,
+ changeEarlyStoppingSetting,
+ deleteEarlyStoppingSetting,
+})(EarlyStopping);
diff --git a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx
index 1e5004372f0..4608676f403 100644
--- a/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx
+++ b/pkg/ui/v1beta1/frontend/src/components/HP/Create/HPParameters.jsx
@@ -14,7 +14,10 @@ import Objective from './Params/Objective';
import TrialTemplate from '../../Common/Create/Params/Trial/TrialTemplate';
import Parameters from './Params/Parameters';
import Algorithm from './Params/Algorithm';
+import EarlyStopping from '../../Common/Create/Params/EarlyStopping';
import MetricsCollectorSpec from '../../Common/Create/Params/MetricsCollector';
+import FormControlLabel from '@material-ui/core/FormControlLabel';
+import Checkbox from '@material-ui/core/Checkbox';
import { submitHPJob } from '../../../actions/hpCreateActions';
@@ -110,6 +113,14 @@ const HPParameters = props => {
data.spec.algorithm.algorithmSettings = [];
addAlgorithmSettings(props.algorithmSettings, data.spec.algorithm.algorithmSettings);
+ // Add early stopping if selected.
+ if (checkedSetEarlyStopping) {
+ data.spec.earlyStopping = {};
+ data.spec.earlyStopping.algorithmName = props.earlyStoppingAlgorithm;
+ data.spec.earlyStopping.algorithmSettings = [];
+ addAlgorithmSettings(props.earlyStoppingSettings, data.spec.earlyStopping.algorithmSettings);
+ }
+
data.spec.parameters = [];
addParameter(props.parameters, data.spec.parameters);
@@ -238,6 +249,12 @@ const HPParameters = props => {
const { classes } = props;
+ const [checkedSetEarlyStopping, setCheckedSetEarlyStopping] = React.useState(false);
+
+ const onCheckBoxChange = event => {
+ setCheckedSetEarlyStopping(event.target.checked);
+ };
+
return (
{/* Common Metadata */}
@@ -251,6 +268,25 @@ const HPParameters = props => {
{SectionInTypography('Algorithm')}
+
+
+ Early Stopping (Optional)
+
+
+
+ }
+ label="Set"
+ />
+
+
+ {checkedSetEarlyStopping &&
}
+
{SectionInTypography('Parameters')}
{SectionInTypography('Metrics Collector Spec')}
@@ -290,6 +326,8 @@ const mapStateToProps = state => {
additionalMetricNames: state[constants.HP_CREATE_MODULE].additionalMetricNames,
metricStrategies: state[constants.HP_CREATE_MODULE].metricStrategies,
algorithmName: state[constants.HP_CREATE_MODULE].algorithmName,
+ earlyStoppingAlgorithm: state[constants.GENERAL_MODULE].earlyStoppingAlgorithm,
+ earlyStoppingSettings: state[constants.GENERAL_MODULE].earlyStoppingSettings,
algorithmSettings: state[constants.HP_CREATE_MODULE].algorithmSettings,
parameters: state[constants.HP_CREATE_MODULE].parameters,
primaryPodLabels: state[constants.GENERAL_MODULE].primaryPodLabels,
diff --git a/pkg/ui/v1beta1/frontend/src/components/HP/Monitor/HPJobTable.jsx b/pkg/ui/v1beta1/frontend/src/components/HP/Monitor/HPJobTable.jsx
index f6deb2ae2fc..d04c5e79767 100644
--- a/pkg/ui/v1beta1/frontend/src/components/HP/Monitor/HPJobTable.jsx
+++ b/pkg/ui/v1beta1/frontend/src/components/HP/Monitor/HPJobTable.jsx
@@ -115,7 +115,7 @@ class HPJobTable extends React.Component {
return (
- {this.props.data.length > 1 && (
+ {this.props.data.length >= 1 && (
diff --git a/pkg/ui/v1beta1/frontend/src/reducers/general.js b/pkg/ui/v1beta1/frontend/src/reducers/general.js
index 5a317544f47..8c0367c58ff 100644
--- a/pkg/ui/v1beta1/frontend/src/reducers/general.js
+++ b/pkg/ui/v1beta1/frontend/src/reducers/general.js
@@ -32,6 +32,10 @@ const initialState = {
suggestion: {},
dialogSuggestionOpen: false,
+ earlyStoppingAlgorithm: 'medianstop',
+ allEarlyStoppingAlgorithms: ['medianstop'],
+ earlyStoppingSettings: [],
+
trialTemplateSourceList: [TEMPLATE_SOURCE_CONFIG_MAP, TEMPLATE_SOURCE_YAML],
trialTemplateSource: 'ConfigMap',
primaryPodLabels: [],
@@ -237,6 +241,34 @@ const generalReducer = (state = initialState, action) => {
dialogExperimentOpen: false,
dialogSuggestionOpen: false,
};
+ // Experiment early stopping actions.
+ case actions.CHANGE_EARLY_STOPPING_ALGORITHM:
+ return {
+ ...state,
+ earlyStoppingAlgorithm: action.algorithmName,
+ };
+ case actions.ADD_EARLY_STOPPING_SETTING:
+ var earlyStoppingSettings = state.earlyStoppingSettings.slice();
+ let setting = { name: '', value: '' };
+ earlyStoppingSettings.push(setting);
+ return {
+ ...state,
+ earlyStoppingSettings: earlyStoppingSettings,
+ };
+ case actions.CHANGE_EARLY_STOPPING_SETTING:
+ earlyStoppingSettings = state.earlyStoppingSettings.slice();
+ earlyStoppingSettings[action.index][action.field] = action.value;
+ return {
+ ...state,
+ earlyStoppingSettings: earlyStoppingSettings,
+ };
+ case actions.DELETE_EARLY_STOPPING_SETTING:
+ earlyStoppingSettings = state.earlyStoppingSettings.slice();
+ earlyStoppingSettings.splice(action.index, 1);
+ return {
+ ...state,
+ earlyStoppingSettings: earlyStoppingSettings,
+ };
// Experiment Trial Template actions.
case templateActions.FETCH_TRIAL_TEMPLATES_SUCCESS:
var trialTemplatesData = action.trialTemplatesData;