From aa5ce358719daddbcc5702e67d1a55878af4ee75 Mon Sep 17 00:00:00 2001 From: Jakob Langdal Date: Tue, 5 Jul 2022 12:13:10 +0000 Subject: [PATCH 1/4] Refactor reducer test --- src/reducers/reducers.test.ts | 354 ++++++++++++++++++---------------- 1 file changed, 185 insertions(+), 169 deletions(-) diff --git a/src/reducers/reducers.test.ts b/src/reducers/reducers.test.ts index cd248794..ce872404 100644 --- a/src/reducers/reducers.test.ts +++ b/src/reducers/reducers.test.ts @@ -60,200 +60,216 @@ 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: [ - { - name: 'Water', - description: 'Wet', - type: 'continuous', - min: 100, - max: 200, - }, + 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, - ], - }, - }) - }) + } - 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: [], - }, + expect(rootReducer(initState, action)).toEqual({ + experiment: { + ...initState.experiment, + changedSinceLastEvaluation: true, + valueVariables: [ + { + name: 'Water', + description: 'Wet', + type: 'continuous', + min: 100, + max: 200, + }, + payload, + ], + }, + }) + }) }) - }) - 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: [ - { - name: 'Icing', - description: 'Sugary', - options: [], + 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)).toEqual({ + experiment: { + ...initState.experiment, + changedSinceLastEvaluation: true, + valueVariables: [], }, + }) + }) + }) + + 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)).toEqual({ + experiment: { + ...initState.experiment, + changedSinceLastEvaluation: true, + categoricalVariables: [ + { + name: 'Icing', + description: 'Sugary', + options: [], + }, + payload, + ], + }, + }) + }) }) - }) - 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)).toEqual({ + experiment: { + ...initState.experiment, + changedSinceLastEvaluation: true, + categoricalVariables: [], + }, + }) + }) }) }) From e3f00d4c7bc6afad12132e6dba49f1e0f47aa262 Mon Sep 17 00:00:00 2001 From: Jakob Langdal Date: Tue, 5 Jul 2022 12:23:28 +0000 Subject: [PATCH 2/4] Automatically set initial points based on input --- src/reducers/experiment-reducers.ts | 39 ++++++++++++++++++++++++++--- src/reducers/reducers.test.ts | 16 ++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/reducers/experiment-reducers.ts b/src/reducers/experiment-reducers.ts index 642fda57..b4528a4f 100644 --- a/src/reducers/experiment-reducers.ts +++ b/src/reducers/experiment-reducers.ts @@ -9,6 +9,9 @@ import { } from '../types/common' import { assertUnreachable } from '../utility' +const calculateInitialPoints = (state: ExperimentType) => + (state.categoricalVariables.length + state.valueVariables.length) * 3 + export type ExperimentAction = | { type: 'setSwVersion' @@ -114,21 +117,35 @@ export const experimentReducer = ( 0, action.payload ) - return { + newState = { ...experimentState, changedSinceLastEvaluation: true, valueVariables: varsAfterAdd, } + return { + ...newState, + optimizerConfig: { + ...experimentState.optimizerConfig, + initialPoints: calculateInitialPoints(newState), + }, + } case 'deleteValueVariable': let varsAfterDelete: ValueVariableType[] = experimentState.valueVariables.slice() let indexOfDelete = experimentState.valueVariables.indexOf(action.payload) varsAfterDelete.splice(indexOfDelete, 1) - return { + newState = { ...experimentState, changedSinceLastEvaluation: true, valueVariables: varsAfterDelete, } + return { + ...newState, + optimizerConfig: { + ...experimentState.optimizerConfig, + initialPoints: calculateInitialPoints(newState), + }, + } case 'addCategorialVariable': let catVarsAfterAdd: CategoricalVariableType[] = experimentState.categoricalVariables.slice() @@ -137,11 +154,18 @@ export const experimentReducer = ( 0, action.payload ) - return { + newState = { ...experimentState, changedSinceLastEvaluation: true, categoricalVariables: catVarsAfterAdd, } + return { + ...newState, + optimizerConfig: { + ...experimentState.optimizerConfig, + initialPoints: calculateInitialPoints(newState), + }, + } case 'deleteCategorialVariable': let catVarsAfterDelete: CategoricalVariableType[] = experimentState.categoricalVariables.slice() @@ -149,11 +173,18 @@ export const experimentReducer = ( action.payload ) catVarsAfterDelete.splice(indexOfCatDelete, 1) - return { + newState = { ...experimentState, changedSinceLastEvaluation: true, categoricalVariables: catVarsAfterDelete, } + return { + ...newState, + optimizerConfig: { + ...experimentState.optimizerConfig, + initialPoints: calculateInitialPoints(newState), + }, + } case 'updateConfiguration': if ( action.payload.initialPoints !== diff --git a/src/reducers/reducers.test.ts b/src/reducers/reducers.test.ts index ce872404..33b9391f 100644 --- a/src/reducers/reducers.test.ts +++ b/src/reducers/reducers.test.ts @@ -178,6 +178,10 @@ describe('experiment reducer', () => { expect(rootReducer(initState, action)).toEqual({ experiment: { ...initState.experiment, + optimizerConfig: { + ...initState.experiment.optimizerConfig, + initialPoints: 9, + }, changedSinceLastEvaluation: true, valueVariables: [ { @@ -212,6 +216,10 @@ describe('experiment reducer', () => { expect(rootReducer(initState, action)).toEqual({ experiment: { ...initState.experiment, + optimizerConfig: { + ...initState.experiment.optimizerConfig, + initialPoints: 3, + }, changedSinceLastEvaluation: true, valueVariables: [], }, @@ -235,6 +243,10 @@ describe('experiment reducer', () => { expect(rootReducer(initState, action)).toEqual({ experiment: { ...initState.experiment, + optimizerConfig: { + ...initState.experiment.optimizerConfig, + initialPoints: 9, + }, changedSinceLastEvaluation: true, categoricalVariables: [ { @@ -265,6 +277,10 @@ describe('experiment reducer', () => { expect(rootReducer(initState, action)).toEqual({ experiment: { ...initState.experiment, + optimizerConfig: { + ...initState.experiment.optimizerConfig, + initialPoints: 3, + }, changedSinceLastEvaluation: true, categoricalVariables: [], }, From 7d22976afb47660a61d6afdf781e211af61846d6 Mon Sep 17 00:00:00 2001 From: Jakob Langdal Date: Wed, 6 Jul 2022 08:01:17 +0000 Subject: [PATCH 3/4] Show initialization state when initial points is zero --- src/components/result-data/result-data.tsx | 1 + 1 file changed, 1 insertion(+) 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 ? ( From f35968004fdf92d5093eb51ff1afe9dcb33f60fa Mon Sep 17 00:00:00 2001 From: Jakob Langdal Date: Wed, 6 Jul 2022 09:48:00 +0000 Subject: [PATCH 4/4] Calculate initial points and suggestion count --- src/reducers/experiment-reducers.ts | 13 +- src/reducers/reducers.test.ts | 176 +++++++++++++++++++--------- 2 files changed, 130 insertions(+), 59 deletions(-) diff --git a/src/reducers/experiment-reducers.ts b/src/reducers/experiment-reducers.ts index 12606cb1..f780995e 100644 --- a/src/reducers/experiment-reducers.ts +++ b/src/reducers/experiment-reducers.ts @@ -11,7 +11,10 @@ import { assertUnreachable } from '../utility' import produce from 'immer' const calculateInitialPoints = (state: ExperimentType) => - (state.categoricalVariables.length + state.valueVariables.length) * 3 + Math.max( + 3, + (state.categoricalVariables.length + state.valueVariables.length) * 3 + ) export type ExperimentAction = | { @@ -95,12 +98,16 @@ export const experimentReducer = produce( 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 @@ -110,6 +117,8 @@ export const experimentReducer = produce( action.payload ) state.optimizerConfig.initialPoints = calculateInitialPoints(state) + state.extras.experimentSuggestionCount = + state.optimizerConfig.initialPoints break case 'deleteCategorialVariable': state.changedSinceLastEvaluation = true @@ -118,6 +127,8 @@ export const experimentReducer = produce( ) 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 33b9391f..89b59cb1 100644 --- a/src/reducers/reducers.test.ts +++ b/src/reducers/reducers.test.ts @@ -175,26 +175,42 @@ describe('experiment reducer', () => { payload, } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - optimizerConfig: { - ...initState.experiment.optimizerConfig, - initialPoints: 9, - }, - changedSinceLastEvaluation: true, - valueVariables: [ - { - name: 'Water', - description: 'Wet', - type: 'continuous', - min: 100, - max: 200, - }, - payload, - ], + expect( + rootReducer(initState, action).experiment.valueVariables + ).toEqual([ + { + name: 'Water', + description: 'Wet', + type: 'continuous', + min: 100, + 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) }) }) @@ -213,17 +229,33 @@ describe('experiment reducer', () => { payload, } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - optimizerConfig: { - ...initState.experiment.optimizerConfig, - initialPoints: 3, - }, - changedSinceLastEvaluation: true, - valueVariables: [], - }, - }) + 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) }) }) @@ -240,24 +272,38 @@ describe('experiment reducer', () => { payload, } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - optimizerConfig: { - ...initState.experiment.optimizerConfig, - initialPoints: 9, - }, - changedSinceLastEvaluation: true, - categoricalVariables: [ - { - name: 'Icing', - description: 'Sugary', - options: [], - }, - 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) }) }) @@ -274,17 +320,31 @@ describe('experiment reducer', () => { payload, } - expect(rootReducer(initState, action)).toEqual({ - experiment: { - ...initState.experiment, - optimizerConfig: { - ...initState.experiment.optimizerConfig, - initialPoints: 3, - }, - changedSinceLastEvaluation: true, - categoricalVariables: [], - }, - }) + 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) }) }) })