class Experiment {
constructor(timesteps, initialState, updateFunctions, sysParams, updateBlocks) {
this.timesteps = timesteps;
this.initialState = initialState;
this.currState = {...initialState}
this.updateFunctions = updateFunctions
this.runs = 1
this.states = {}
this.statesPerRun = []
this.pastSteps = []
this.currStep = 0;
this.sysParams = sysParams
if (updateBlocks) {
this.updateBlocks = updateBlocks
} else {
this.updateBlocks = [[]]
Object.keys(initialState).forEach((n) => {
this.updateBlocks[0].push(n)
})
}
Object.keys(this.updateFunctions).forEach((stateVar) => {
let fString = this.updateFunctions[stateVar]
this.updateFunctions[stateVar] = function(s, t) {
let context = {...s, ...sysParams, timestep: t}
return math.evaluate(fString, context)
}
})
this.initStateTransitions()
}
initializeState() {
Object.keys(this.initialState).forEach((stateKey) => {
this.states[stateKey] = [{key: stateKey, value: this.initialState[stateKey], time: 0}]
})
}
getAllStates() {
return this.states
}
initStateTransitions() {
this.transition = function(currState, currStep) {
const snapshotState = {...currState}
var newState = Object.assign({}, currState)
Object.keys(newState).forEach((name) => {
if (this.updateFunctions.hasOwnProperty(name)) {
let nextState = this.updateFunctions[name].call(this, {...snapshotState}, currStep)
newState[name] = snapshotState[name] + nextState
} else {
throw new Error("No update function for state variable " + name)
}
})
return newState
}
}
update() {
this.updateBlocks.forEach((block) => {
let context = {
currState: {},
currStep: this.currStep
}
let currState = {...this.currState}
block.forEach((stateVarName) => {
context.currState[stateVarName] = currState[stateVarName]
})
context.currState = {...this.transition(currState, context.currStep)}
this.currState = {...context.currState}
})
Object.keys(this.currState).forEach((stateKey) => {
this.states[stateKey].push({key: stateKey, value: this.currState[stateKey], time: this.currStep})
})
this.pastSteps.push(this.currStep)
this.currStep += 1;
}
run() {
if (Array.isArray(this.initialState)) {
let results = []
this.initialStateSequence = [...this.initialState]
this.initialStateSequence.forEach((state) => {
this.initialState = {...state}
this.currState = {...state}
this.initializeState()
for (let i = 1; i <= this.timesteps; i++) {
this.currStep = i;
this.update();
}
results.push(this.getAllStates())
this.states = {}
})
return results
} else {
this.initializeState()
for (let i = 1; i <= this.timesteps; i++) {
this.currStep = i;
this.update();
}
}
}
plotTimeSeries(chartName){
let stateTraces = this.getAllStates()
let traces = Object.keys(stateTraces).map((traceKey) => {
let trace = stateTraces[traceKey]
let traceObj = {type: 'scatter', x: [], y: []}
trace.forEach((stateObj) => {
traceObj.x.push(stateObj.time)
traceObj.y.push(stateObj.value)
})
return traceObj
})
//return traces
return plotly.newPlot(chartName, traces)
}
plotPhaseSpace(chartName, stateTraces) {
let traceObj = {
type: 'scatter',
x: [],
y: []
}
let traces = []
let stateVarNames = Object.keys(this.initialState);
if (!!stateTraces && Array.isArray(stateTraces)) {
traces = stateTraces.map((stateTraceObject) => {
let objectForChart = Object.assign({}, traceObj)
stateVarNames.forEach((n, idx) => {
if (idx === 0) {
objectForChart.x = stateTraces[n].map(stateAtTime => stateAtTime.value)
} else if (idx === 1) {
objectForChart.y = stateTraces[n].map(stateAtTime => stateAtTime.value)
} else if (idx === 2) {
objectForChart.z = stateTraces[n].map(stateAtTime => stateAtTime.value)
}
})
return objectForChart
})
} else {
if (!stateTraces) {
stateTraces = this.getAllStates();
}
stateVarNames.forEach((n, idx) => {
if (idx === 0) {
traceObj.x = stateTraces[n].map(stateAtTime => stateAtTime.value)
} else if (idx === 1) {
traceObj.y = stateTraces[n].map(stateAtTime => stateAtTime.value)
} else if (idx === 2) {
traceObj.z = stateTraces[n].map(stateAtTime => stateAtTime.value)
}
})
traces = [traceObj]
}
//return traces
return plotly.newPlot(chartName, traces)
}
}