diff --git a/src/components/result-data/result-data.tsx b/src/components/result-data/result-data.tsx index e7ce922b..ad0228fc 100644 --- a/src/components/result-data/result-data.tsx +++ b/src/components/result-data/result-data.tsx @@ -38,6 +38,7 @@ export const ResultData = (props: ResultDataProps) => { (experiment.extras['experimentSuggestionCount'] as number) ?? 1 const isInitializing = + experiment.optimizerConfig.initialPoints === 0 || experiment.dataPoints.length < experiment.optimizerConfig.initialPoints const summary = isInitializing ? ( diff --git a/src/reducers/experiment-reducers.ts b/src/reducers/experiment-reducers.ts index ffda2262..f780995e 100644 --- a/src/reducers/experiment-reducers.ts +++ b/src/reducers/experiment-reducers.ts @@ -10,6 +10,12 @@ import { import { assertUnreachable } from '../utility' import produce from 'immer' +const calculateInitialPoints = (state: ExperimentType) => + Math.max( + 3, + (state.categoricalVariables.length + state.valueVariables.length) * 3 + ) + export type ExperimentAction = | { type: 'setSwVersion' @@ -91,11 +97,17 @@ export const experimentReducer = produce( 0, action.payload ) + state.optimizerConfig.initialPoints = calculateInitialPoints(state) + state.extras.experimentSuggestionCount = + state.optimizerConfig.initialPoints break case 'deleteValueVariable': state.changedSinceLastEvaluation = true let indexOfDelete = state.valueVariables.indexOf(action.payload) state.valueVariables.splice(indexOfDelete, 1) + state.optimizerConfig.initialPoints = calculateInitialPoints(state) + state.extras.experimentSuggestionCount = + state.optimizerConfig.initialPoints break case 'addCategorialVariable': state.changedSinceLastEvaluation = true @@ -104,6 +116,9 @@ export const experimentReducer = produce( 0, action.payload ) + state.optimizerConfig.initialPoints = calculateInitialPoints(state) + state.extras.experimentSuggestionCount = + state.optimizerConfig.initialPoints break case 'deleteCategorialVariable': state.changedSinceLastEvaluation = true @@ -111,6 +126,9 @@ export const experimentReducer = produce( action.payload ) state.categoricalVariables.splice(indexOfCatDelete, 1) + state.optimizerConfig.initialPoints = calculateInitialPoints(state) + state.extras.experimentSuggestionCount = + state.optimizerConfig.initialPoints break case 'updateConfiguration': state.changedSinceLastEvaluation = true diff --git a/src/reducers/reducers.test.ts b/src/reducers/reducers.test.ts index cd248794..89b59cb1 100644 --- a/src/reducers/reducers.test.ts +++ b/src/reducers/reducers.test.ts @@ -60,118 +60,124 @@ describe('experiment reducer', () => { }, } - it('should update whole experiment', async () => { - const payload: ExperimentType = { - id: '5678', - changedSinceLastEvaluation: false, - info: { - swVersion: '', - name: 'Not cake', - description: 'Not yummy', - dataFormatVersion: '1.0.0', - }, - categoricalVariables: [ - { - name: 'Not icing', - description: 'Not sugary', - options: [], + describe('updateExperiment', () => { + it('should update whole experiment', async () => { + const payload: ExperimentType = { + id: '5678', + changedSinceLastEvaluation: false, + info: { + swVersion: '', + name: 'Not cake', + description: 'Not yummy', + dataFormatVersion: '1.0.0', }, - ], - valueVariables: [ - { - type: 'continuous', - name: 'Not water', - description: 'Not wet', - min: 101, - max: 201, + categoricalVariables: [ + { + name: 'Not icing', + description: 'Not sugary', + options: [], + }, + ], + valueVariables: [ + { + type: 'continuous', + name: 'Not water', + description: 'Not wet', + min: 101, + max: 201, + }, + ], + scoreVariables: [], + optimizerConfig: { + baseEstimator: 'GP', + acqFunc: 'gp_hedge', + initialPoints: 4, + kappa: 1.96, + xi: 0.01, }, - ], - scoreVariables: [], - optimizerConfig: { - baseEstimator: 'GP', - acqFunc: 'gp_hedge', - initialPoints: 4, - kappa: 1.96, - xi: 0.01, - }, - results: { - id: '123', - next: [], - plots: [], - pickled: '123', - expectedMinimum: [], - extras: {}, - }, - dataPoints: [], - extras: { - experimentSuggestionCount: 1, - }, - } + results: { + id: '123', + next: [], + plots: [], + pickled: '123', + expectedMinimum: [], + extras: {}, + }, + dataPoints: [], + extras: { + experimentSuggestionCount: 1, + }, + } - const action: ExperimentAction = { - type: 'updateExperiment', - payload, - } + const action: ExperimentAction = { + type: 'updateExperiment', + payload, + } - expect(rootReducer(initState, action)).toEqual({ - experiment: payload, + expect(rootReducer(initState, action)).toEqual({ + experiment: payload, + }) }) }) - it('should update name', async () => { - const action: ExperimentAction = { - type: 'updateExperimentName', - payload: 'Muffins', - } + describe('updateExperimentName', () => { + it('should update name', async () => { + const action: ExperimentAction = { + type: 'updateExperimentName', + payload: 'Muffins', + } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - id: '1234', - info: { - ...initState.experiment.info, - name: 'Muffins', + expect(rootReducer(initState, action)).toEqual({ + experiment: { + ...initState.experiment, + id: '1234', + info: { + ...initState.experiment.info, + name: 'Muffins', + }, }, - }, + }) }) }) - it('should update description', async () => { - const action: ExperimentAction = { - type: 'updateExperimentDescription', - payload: 'Tasty', - } + describe('updateExperimentDescription', () => { + it('should update description', async () => { + const action: ExperimentAction = { + type: 'updateExperimentDescription', + payload: 'Tasty', + } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - info: { - ...initState.experiment.info, - description: 'Tasty', + expect(rootReducer(initState, action)).toEqual({ + experiment: { + ...initState.experiment, + info: { + ...initState.experiment.info, + description: 'Tasty', + }, }, - }, + }) }) }) - it('should add value variable', async () => { - const payload: ValueVariableType = { - type: 'continuous', - name: 'Flour', - description: 'Wet', - min: 300, - max: 400, - } - - const action: ExperimentAction = { - type: 'addValueVariable', - payload, - } - - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - changedSinceLastEvaluation: true, - valueVariables: [ + describe('Variables', () => { + describe('addValueVariable', () => { + it('should add value variable', async () => { + const payload: ValueVariableType = { + type: 'continuous', + name: 'Flour', + description: 'Wet', + min: 300, + max: 400, + } + + const action: ExperimentAction = { + type: 'addValueVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.valueVariables + ).toEqual([ { name: 'Water', description: 'Wet', @@ -180,80 +186,166 @@ describe('experiment reducer', () => { max: 200, }, payload, - ], - }, + ]) + }) + + it('should set initial points and suggestion', async () => { + const payload: ValueVariableType = { + type: 'continuous', + name: 'Flour', + description: 'Wet', + min: 300, + max: 400, + } + + const action: ExperimentAction = { + type: 'addValueVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.optimizerConfig + .initialPoints + ).toEqual(9) + expect( + rootReducer(initState, action).experiment.extras + .experimentSuggestionCount + ).toEqual(9) + }) }) - }) - it('should delete value variable', async () => { - const payload: ValueVariableType = { - type: 'continuous', - name: 'Water', - description: 'Wet', - min: 100, - max: 200, - } - - const action: ExperimentAction = { - type: 'deleteValueVariable', - payload, - } - - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - changedSinceLastEvaluation: true, - valueVariables: [], - }, + describe('deleteValueVariable', () => { + it('should delete value variable', async () => { + const payload: ValueVariableType = { + type: 'continuous', + name: 'Water', + description: 'Wet', + min: 100, + max: 200, + } + + const action: ExperimentAction = { + type: 'deleteValueVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.valueVariables + ).toEqual([]) + }) + + it('should reset initial points and suggestion', async () => { + const payload: ValueVariableType = { + type: 'continuous', + name: 'Water', + description: 'Wet', + min: 100, + max: 200, + } + + const action: ExperimentAction = { + type: 'deleteValueVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.optimizerConfig + .initialPoints + ).toEqual(3) + expect( + rootReducer(initState, action).experiment.extras + .experimentSuggestionCount + ).toEqual(3) + }) }) - }) - it('should add categorial variable', async () => { - const payload: CategoricalVariableType = { - name: 'Fat', - description: 'Fatty', - options: [], - } - - const action: ExperimentAction = { - type: 'addCategorialVariable', - payload, - } - - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - changedSinceLastEvaluation: true, - categoricalVariables: [ + describe('addCategorialVariable', () => { + it('should add categorial variable', async () => { + const payload: CategoricalVariableType = { + name: 'Fat', + description: 'Fatty', + options: [], + } + + const action: ExperimentAction = { + type: 'addCategorialVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.categoricalVariables + ).toEqual([ { name: 'Icing', description: 'Sugary', options: [], }, payload, - ], - }, + ]) + }) + + it('should set initial points and suggestion', async () => { + const payload: CategoricalVariableType = { + name: 'Fat', + description: 'Fatty', + options: [], + } + + const action: ExperimentAction = { + type: 'addCategorialVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.optimizerConfig + .initialPoints + ).toEqual(9) + expect( + rootReducer(initState, action).experiment.extras + .experimentSuggestionCount + ).toEqual(9) + }) }) - }) - it('should delete categorical variable', async () => { - const payload: CategoricalVariableType = { - name: 'Icing', - description: 'Sugary', - options: [], - } - - const action: ExperimentAction = { - type: 'deleteCategorialVariable', - payload, - } - - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - changedSinceLastEvaluation: true, - categoricalVariables: [], - }, + describe('deleteCategorialVariable', () => { + it('should delete categorical variable', async () => { + const payload: CategoricalVariableType = { + name: 'Icing', + description: 'Sugary', + options: [], + } + + const action: ExperimentAction = { + type: 'deleteCategorialVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.categoricalVariables + ).toEqual([]) + }) + + it('should reset suggestion count and initial points', async () => { + const payload: CategoricalVariableType = { + name: 'Icing', + description: 'Sugary', + options: [], + } + + const action: ExperimentAction = { + type: 'deleteCategorialVariable', + payload, + } + + expect( + rootReducer(initState, action).experiment.optimizerConfig + .initialPoints + ).toEqual(3) + expect( + rootReducer(initState, action).experiment.extras + .experimentSuggestionCount + ).toEqual(3) + }) }) })