diff --git a/documents/minutes/Project Meeting - 4.docx b/documents/minutes/Project Meeting - 4.docx index 05be196..db53806 100644 Binary files a/documents/minutes/Project Meeting - 4.docx and b/documents/minutes/Project Meeting - 4.docx differ diff --git a/documents/minutes/~$oject Meeting - 4.docx b/documents/minutes/~$oject Meeting - 4.docx deleted file mode 100644 index aa60e40..0000000 Binary files a/documents/minutes/~$oject Meeting - 4.docx and /dev/null differ diff --git a/packages/frontend-common/src/state/actions/action.types.tsx b/packages/frontend-common/src/state/actions/action.types.tsx index 7a114f8..79a8b04 100644 --- a/packages/frontend-common/src/state/actions/action.types.tsx +++ b/packages/frontend-common/src/state/actions/action.types.tsx @@ -16,12 +16,12 @@ export const FetchRunsType = `${microFrontendMessageId}:fetch_runs`; export const FetchRunsResultType = `${microFrontendMessageId}:fetch_runs_result`; export const FetchRunResultType = `${microFrontendMessageId}:fetch_run_result`; export const VisualisationNameType = `${microFrontendMessageId}:set_visualisation_name`; +export const DataRequestType = `${microFrontendMessageId}:data_request`; export const DataType = `${microFrontendMessageId}:set_data`; // Socket related actions export const InitiateSocketSuccessType = `${microFrontendMessageId}:initiate_socket_success`; export const DisconnectSocketSuccessType = `${microFrontendMessageId}:disconnect_socket_success`; -// export const RunGenerationSuccessType = `${microFrontendMessageId}:run_generation_success`; export const SubscribedType = `${microFrontendMessageId}:set_subscribed`; export interface RegisterRoutePayload { diff --git a/packages/frontend-common/src/state/actions/socket.actions.tsx b/packages/frontend-common/src/state/actions/socket.actions.tsx index 7898640..83055d0 100644 --- a/packages/frontend-common/src/state/actions/socket.actions.tsx +++ b/packages/frontend-common/src/state/actions/socket.actions.tsx @@ -2,6 +2,7 @@ import { Action } from "redux"; import ioclient from "socket.io-client"; import { ActionType, ThunkResult } from "../state.types"; import { + DataRequestType, DisconnectSocketSuccessType, InitiateSocketSuccessType, @@ -24,15 +25,6 @@ export const disconnectSocketSuccess = (): Action => ({ type: DisconnectSocketSuccessType, }); -// export const runGenerationSuccess = ( -// generation: number -// ): ActionType => ({ -// type: RunGenerationSuccessType, -// payload: { -// generation, -// }, -// }); - export const setSubscribed = ( subscribed: boolean ): ActionType => ({ @@ -90,6 +82,10 @@ export const subscribeToGenerations = ( }; }; +export const dataRequest = (): Action => ({ + type: DataRequestType, +}); + // Retrieve the data from a run given the ID and generation number export const fetchData = ( dataId: string, @@ -98,8 +94,10 @@ export const fetchData = ( return async (dispatch, getState) => { const { socket } = getState().frontend.configuration; - // socketConnected if (socket && socket.connected) { + // Dispatch the fetch data request + dispatch(dataRequest()); + // Emit the socket event socket.emit("data", { dataId, diff --git a/packages/frontend-common/src/state/reducers/common.reducer.tsx b/packages/frontend-common/src/state/reducers/common.reducer.tsx index 8571f53..c601d31 100644 --- a/packages/frontend-common/src/state/reducers/common.reducer.tsx +++ b/packages/frontend-common/src/state/reducers/common.reducer.tsx @@ -1,6 +1,7 @@ import * as log from "loglevel"; import { DataPayload, + DataRequestType, DataType, DisconnectSocketSuccessType, FetchRunResultType, @@ -33,14 +34,13 @@ export const initialState: FrontendState = { }, settingsLoaded: false, socket: null, - // socketConnected: false, subscribed: false, }, runs: [], selectedRun: null, selectedVisualisation: "", data: null, - // currentGeneration: 0, + fetchingData: false, }; const updatePlugins = ( @@ -143,7 +143,6 @@ export function handleInitiateSocket( configuration: { ...state.configuration, socket: payload.socket, - // socketConnected: true, }, }; } @@ -154,21 +153,10 @@ export function handleDisconnectSocket(state: FrontendState): FrontendState { configuration: { ...state.configuration, socket: null, - // socketConnected: false, }, }; } -// export function handleRunGeneration( -// state: FrontendState, -// payload: RunGenerationPayload -// ): FrontendState { -// return { -// ...state, -// currentGeneration: payload.generation, -// }; -// } - export function handleSubscribed( state: FrontendState, payload: SubscribedPayload @@ -182,6 +170,13 @@ export function handleSubscribed( }; } +export function handleDataRequest(state: FrontendState): FrontendState { + return { + ...state, + fetchingData: true, + }; +} + export function handleData( state: FrontendState, payload: DataPayload @@ -189,6 +184,7 @@ export function handleData( return { ...state, data: payload.data, + fetchingData: false, }; } @@ -201,9 +197,9 @@ const CommonReducer = createReducer(initialState, { [FetchRunResultType]: handleFetchRun, [InitiateSocketSuccessType]: handleInitiateSocket, [DisconnectSocketSuccessType]: handleDisconnectSocket, - // [RunGenerationSuccessType]: handleRunGeneration, [VisualisationNameType]: handleVisualisationName, [SubscribedType]: handleSubscribed, + [DataRequestType]: handleDataRequest, [DataType]: handleData, }); diff --git a/packages/frontend-common/src/state/state.types.tsx b/packages/frontend-common/src/state/state.types.tsx index e646141..098a7e4 100644 --- a/packages/frontend-common/src/state/state.types.tsx +++ b/packages/frontend-common/src/state/state.types.tsx @@ -38,16 +38,13 @@ export interface FrontendState { urls: SettingsUrls; settingsLoaded: boolean; socket: SocketIOClient.Socket | null; - // TODO: Add subscribed to state? - // TODO: Remove socketConnected - // socketConnected: boolean; subscribed: boolean; }; runs: Run[]; selectedRun: Run | null; selectedVisualisation: string; data: Data; - // currentGeneration: number; + fetchingData: boolean; } export interface StateType { diff --git a/packages/frontend-plugin/src/visualisation.component.tsx b/packages/frontend-plugin/src/visualisation.component.tsx index 3d179e0..e8638de 100644 --- a/packages/frontend-plugin/src/visualisation.component.tsx +++ b/packages/frontend-plugin/src/visualisation.component.tsx @@ -24,13 +24,14 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { const chartRef: React.RefObject = React.createRef(); const margin = { - top: 10, + top: 50, right: 30, bottom: 30, left: 60, }; + const width = 760 - margin.left - margin.right; - const height = 800 - margin.top - margin.bottom; + const height = 450 - margin.top - margin.bottom; const xScale: d3.ScaleLinear = d3 .scaleLinear() @@ -178,7 +179,7 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { .attr("cx", (d) => xScale(d[0]) as number) .attr("cy", (d) => yScale(d[1]) as number) .attr("r", 3) - .style("fill", "#69b3a2"); + .style("fill", "blue"); }, [currentChart, xScale, yScale] ); diff --git a/packages/frontend/src/components/mainAppBar.component.tsx b/packages/frontend/src/components/mainAppBar.component.tsx index 1088987..00652f9 100644 --- a/packages/frontend/src/components/mainAppBar.component.tsx +++ b/packages/frontend/src/components/mainAppBar.component.tsx @@ -1,7 +1,6 @@ import { AppBar, CssBaseline, Link, Toolbar, Typography } from '@material-ui/core'; import { createStyles, makeStyles } from '@material-ui/core/styles'; import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; const useStyles = makeStyles(() => createStyles({ @@ -20,7 +19,7 @@ const MainAppBar = (): React.ReactElement => { - + Visualising Optimisation Data diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index 619269e..d1cb86c 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -17,6 +17,8 @@ import { Action, AnyAction } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; // TODO: Use queue (in state?) +// TODO: Try to instead of having an object, store the list in the component +// then perform actions using setState on the list. // class GenerationQueue { // generations: number[]; @@ -25,13 +27,14 @@ import { ThunkDispatch } from 'redux-thunk'; // } // // Add a generation (enqueue) -// append(generation: number): void { +// push(generation: number): void { // this.generations.push(generation); // } // // Remove a generation (dequeue) // pop(): number | undefined { // if (!this.isEmpty()) { +// console.log('Returning number'); // return this.generations.shift(); // } else { // return -1; @@ -60,7 +63,6 @@ interface VCDispatchProps { initiateSocket: (runId: string) => Promise; subscribeToGenerations: (runId: string) => Promise; fetchData: (dataId: string, generation: number) => Promise; - // setCurrentGeneration: (generation: number) => Action; setSubscribed: (subscribed: boolean) => Action; setData: (data: Data) => Action; } @@ -69,9 +71,8 @@ interface VCStateProps { selectedRun: Run | null; selectedVisualisation: string; socket: SocketIOClient.Socket | null; - // socketConnected: boolean; - // currentGeneration: number; subscribed: boolean; + fetchingData: boolean; } type VCProps = VCViewProps & VCDispatchProps & VCStateProps; @@ -79,7 +80,6 @@ type VCProps = VCViewProps & VCDispatchProps & VCStateProps; const VisualisationContainer = (props: VCProps): React.ReactElement => { const { socket, - // socketConnected, initiateSocket, fetchRun, setVisualisationName, @@ -88,25 +88,52 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { selectedRun, selectedVisualisation, subscribeToGenerations, - // currentGeneration, fetchData, - // setCurrentGeneration, subscribed, setSubscribed, setData, + fetchingData, } = props; const [loadedRun, setLoadedRun] = React.useState(false); - // const [subscribed, setSubscribed] = React.useState(false); - const [currentGeneration, setCurrentGeneration] = React.useState(-1); + // Create a generation queue object in state + // const [generationQueue] = React.useState(new GenerationQueue()); + const [generationQueue, setGenerationQueue] = React.useState([]); + + const pushToGQ = (generation: number) => { + setGenerationQueue([...generationQueue, generation]); + }; + + // Remove a generation (dequeue) + const popFromGQ = (): number => { + if (!isEmpty()) { + const n = generationQueue.shift(); + console.log('Returning n: ', -1); + if (n) { + return n; + } else { + return -1; + } + } else { + console.log('Queue empty'); + return -1; + } + }; + + // Check if the queue is empty + const isEmpty = (): boolean => { + if (generationQueue.length === 0) { + return true; + } else { + return false; + } + }; + // TODO: * Be careful about where we place receiving data from sockets (prevent duplicate data) - // * Prevent multiple event handlers (subcribed to state, replace with socketConnected?) - // DONE: Remove unnecessary items from state e.g. socketConnected? (replace with socket.connected?) + // * Prevent multiple event handlers (subcribed to state) // TODO: * Wait until data has been received from server after firing request, before firing next request - // DONE: Remove/correct currentGeneration in state? - // DONE: currentGeneration should not be in state? Just in component. // Load run information React.useEffect(() => { @@ -150,12 +177,16 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { // When we receive "subscribed" from // server attach the callback function socket.on('generation', (generation: number) => { - // console.log('Generation received: ', generation); - setCurrentGeneration(generation); + // setCurrentGeneration(generation); + + // Add the generation to the queue. + // generationQueue.push(generation); + pushToGQ(generation); + console.log('Generation added to queue: ', generation); + console.log('Queue: ', generationQueue); }); // Handle the data response event - // eslint-disable-next-line @typescript-eslint/no-explicit-any socket.on('data', (data: Data) => { console.log('Data received for current generation: ', data); setData(data); @@ -169,18 +200,35 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { // TODO: Ensure this does not request multiple times // Handle fetching new data on generation changes React.useEffect(() => { + console.log('Got new update'); + // Fetch the data for the new generation if (selectedRun && socket && socket.connected) { if (currentGeneration < 0) { // Initialise current generation with current run information setCurrentGeneration(selectedRun.currentGeneration); console.log('Set current generation to: ', selectedRun.currentGeneration); + + pushToGQ(selectedRun.currentGeneration); + console.log('Queue: ', generationQueue); } else { - // console.log('Requesting data'); - fetchData(selectedRun.dataId, currentGeneration); + // Fetch data if there are currently no requests + if (!fetchingData) { + // Get the next generation to fetch + console.log(generationQueue); + const generation = popFromGQ(); + console.log('generation from queue: ', generation); + if (generation && generation !== -1) { + // TODO: Is this correct calling setCurrentGeneration here? + setCurrentGeneration(generation); + + console.log('Requesting data'); + fetchData(selectedRun.dataId, generation); + } + } } } - }, [selectedRun, socket, currentGeneration]); + }, [selectedRun, socket, currentGeneration, fetchingData, generationQueue]); return ( @@ -231,7 +279,6 @@ const mapDispatchToProps = (dispatch: ThunkDispatch) initiateSocket: (runId: string) => dispatch(initiateSocket(runId)), subscribeToGenerations: (runId: string) => dispatch(subscribeToGenerations(runId)), fetchData: (dataId: string, generation: number) => dispatch(fetchData(dataId, generation)), - // setCurrentGeneration: (generation: number) => dispatch(runGenerationSuccess(generation)), setSubscribed: (subscribed: boolean) => dispatch(setSubscribed(subscribed)), setData: (data: Data) => dispatch(setData(data)), }); @@ -239,11 +286,10 @@ const mapDispatchToProps = (dispatch: ThunkDispatch) const mapStateToProps = (state: StateType): VCStateProps => { return { socket: state.frontend.configuration.socket, - // socketConnected: state.frontend.configuration.socketConnected, selectedRun: state.frontend.selectedRun, selectedVisualisation: state.frontend.selectedVisualisation, - // currentGeneration: state.frontend.currentGeneration, subscribed: state.frontend.configuration.subscribed, + fetchingData: state.frontend.fetchingData, }; }; diff --git a/scripts/client.py b/scripts/client.py index 0d72e2d..108ee41 100644 --- a/scripts/client.py +++ b/scripts/client.py @@ -9,7 +9,6 @@ # http://opt-vis-backend.herokuapp.com/ # http://localhost:9000 BACKEND_URL = "http://localhost:9000" -# 40185 # PORT = "33585" # WEBSOCKET_URL = BACKEND_URL + ":" + PORT API_URL = BACKEND_URL + "/api" @@ -40,7 +39,6 @@ def on_disconnect(self): def on_save(self, data): # print(data) if (data['saved']): - # print("Saved data for generation: ", data['generation']) self.sending_data = False else: print("Unable to save data: ", data['message']) @@ -60,7 +58,6 @@ def process_queue(self): data = { "runId": self.run_id, "dataId": self.data_id, - # "generation": self.current_generation, "batch": batch } self.emit("data", data) diff --git a/scripts/sample_data.py b/scripts/dtlz1.py similarity index 87% rename from scripts/sample_data.py rename to scripts/dtlz1.py index f25c6c1..88062a6 100644 --- a/scripts/sample_data.py +++ b/scripts/dtlz1.py @@ -11,11 +11,7 @@ dtlz1_data = pickle.load(dtlz1_file) # Population size is 100 -# dtlz2_file = open('data/DTLZ2DataFile.pkl', 'rb') -# dtlz2_data = pickle.load(dtlz2_file) - print("Total population size: ", len(dtlz1_data)) -# print("Total population size: ", len(dtlz2_data)) populationSize = 100 totalGenerations = 282 @@ -50,7 +46,7 @@ else: # Add the data to the client queue to send # print("Sending generation: ", generation) - optimiserClient.addBatch(data_batch) # generation + optimiserClient.addBatch(data_batch) data_batch = [] # generation += 1 diff --git a/scripts/dtlz2.py b/scripts/dtlz2.py new file mode 100644 index 0000000..7cf39aa --- /dev/null +++ b/scripts/dtlz2.py @@ -0,0 +1,51 @@ +import os +import pickle +import time + +import client + +dirname = os.path.dirname(__file__) + +# Open the optimisation data file +dtlz2_file = open(os.path.join(dirname, 'data/DTLZ2DataFile.pkl'), 'rb') +dtlz2_data = pickle.load(dtlz2_file) + +# Population size is 100 +# print("Total population size: ", len(dtlz2_data)) + +populationSize = 100 +totalGenerations = 54 + +# Create an optimiser client +optimiserClient = client.OptimiserClient(populationSize) + +# Algorithm parameters +algorithmParameters = { + "Function Evaluations": 5000, + "sbxProb": 0.8, + "pmProb": 0.1 +} + +# Create the optimisation run +optimiserClient.createRun("Pareto front estimation of DTLZ2", "DTLZ2", + "NGSA-II", populationSize, totalGenerations, algorithmParameters, ["frontend-plugin"]) + +# Send generation data to the server +count = 1 +data_batch = [] +for values in dtlz2_data: + # Add the data to the batch to send + data_batch.append(values.tolist()) + + if (count < populationSize): + count += 1 + else: + # Add the data to the client queue to send + # print("Sending generation: ", generation) + optimiserClient.addBatch(data_batch) + data_batch = [] + + count = 1 + # print("Next Generation: ", generation) + +print("Completed adding items to queue, wait until queue has been processed")