@abi-software/simulationvuer 0.7.0-vue-3-alpha.5 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/simulationvuer.js +41108 -7828
- package/dist/simulationvuer.umd.cjs +97 -14
- package/dist/style.css +1 -1
- package/package.json +15 -3
- package/src/App.vue +24 -11
- package/src/components/SimulationVuer.vue +463 -126
- package/src/components/SimulationVuerInput.vue +1 -1
- package/src/components/common.js +54 -15
- package/src/components/json.js +68 -66
- package/src/components/libopencor.js +16 -0
- package/src/components/libopencor.wasm +0 -0
- package/src/main.js +10 -13
- package/vuese-generator.js +66 -0
- package/src/components/ui.js +0 -85
|
@@ -19,13 +19,16 @@
|
|
|
19
19
|
/>
|
|
20
20
|
</div>
|
|
21
21
|
<div class="primary-button">
|
|
22
|
-
<el-button type="primary" size="small" @click="startSimulation()">Run Simulation</el-button>
|
|
22
|
+
<el-button type="primary" size="small" @click="startSimulation()" v-if="!this.libopencor">Run Simulation</el-button>
|
|
23
23
|
</div>
|
|
24
24
|
<div class="secondary-button" v-if="uuid">
|
|
25
25
|
<el-button size="small" @click="runOnOsparc()">Run on oSPARC</el-button>
|
|
26
26
|
</div>
|
|
27
27
|
<div class="secondary-button">
|
|
28
|
-
<el-button size="small" @click="viewDataset()">View Dataset</el-button>
|
|
28
|
+
<el-button size="small" @click="viewDataset()" v-if="typeof this.id === 'number'">View Dataset</el-button>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="secondary-button">
|
|
31
|
+
<el-button size="small" @click="viewWorkspace()" v-if="typeof this.id !== 'number'">View Workspace</el-button>
|
|
29
32
|
</div>
|
|
30
33
|
<p class="default note" v-if="uuid">Additional parameters are available on oSPARC</p>
|
|
31
34
|
</div>
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
<PlotVuer v-for="(outputPlot, index) in simulationUiInfo.output.plots"
|
|
34
37
|
:key="`output-${index}`"
|
|
35
38
|
:metadata="plotMetadata(index)"
|
|
36
|
-
:data-source="{data:
|
|
39
|
+
:data-source="{data: simulationResults[index]}"
|
|
37
40
|
:plotLayout="layout[index]"
|
|
38
41
|
:plotType="'plotly-only'"
|
|
39
42
|
:selectorUi="false"
|
|
@@ -51,11 +54,24 @@ import { PlotVuer } from "@abi-software/plotvuer";
|
|
|
51
54
|
import "@abi-software/plotvuer/dist/style.css";
|
|
52
55
|
import SimulationVuerInput from "./SimulationVuerInput.vue";
|
|
53
56
|
import { ElButton, ElDivider, ElLoading } from "element-plus";
|
|
54
|
-
import { evaluateValue,
|
|
57
|
+
import { evaluateValue, finaliseUi, OPENCOR_SOLVER_NAME } from "./common.js";
|
|
55
58
|
import { validJson } from "./json.js";
|
|
56
|
-
import
|
|
59
|
+
import libOpenCOR from "./libopencor.js";
|
|
60
|
+
import { toRaw } from "vue";
|
|
61
|
+
import { create, all } from "mathjs";
|
|
62
|
+
|
|
63
|
+
const LIBOPENCOR_SOLVER = "libOpenCOR";
|
|
64
|
+
const OSPARC_SOLVER = "oSPARC";
|
|
65
|
+
const PMR_URL = "https://models.physiomeproject.org/";
|
|
57
66
|
|
|
67
|
+
const math = create(all, {});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* SimulationVuer
|
|
71
|
+
*/
|
|
58
72
|
export default {
|
|
73
|
+
LIBOPENCOR_SOLVER: LIBOPENCOR_SOLVER,
|
|
74
|
+
OSPARC_SOLVER: OSPARC_SOLVER,
|
|
59
75
|
name: "SimulationVuer",
|
|
60
76
|
components: {
|
|
61
77
|
PlotVuer,
|
|
@@ -65,55 +81,82 @@ export default {
|
|
|
65
81
|
ElLoading,
|
|
66
82
|
},
|
|
67
83
|
props: {
|
|
84
|
+
/**
|
|
85
|
+
* The the URL to the API location.
|
|
86
|
+
*/
|
|
68
87
|
apiLocation: {
|
|
69
88
|
required: true,
|
|
70
89
|
type: String,
|
|
71
90
|
},
|
|
91
|
+
/**
|
|
92
|
+
* The ID of the simulation-based dataset.
|
|
93
|
+
*/
|
|
72
94
|
id: {
|
|
73
95
|
required: true,
|
|
74
|
-
|
|
96
|
+
},
|
|
97
|
+
/**
|
|
98
|
+
* The preferred solver to use for OpenCOR-based simulations. This property
|
|
99
|
+
* is optional and defaults to "oSPARC". The only value that is currently
|
|
100
|
+
* supported is "libOpenCOR". Any other value will default to "oSPARC".
|
|
101
|
+
*/
|
|
102
|
+
preferredSolver: {
|
|
103
|
+
type: String,
|
|
104
|
+
default: OSPARC_SOLVER,
|
|
75
105
|
},
|
|
76
106
|
},
|
|
77
107
|
data: function() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
// Retrieve some information about the dataset.
|
|
109
|
+
|
|
110
|
+
if (this.id > 0) {
|
|
111
|
+
const xmlhttp = new XMLHttpRequest();
|
|
112
|
+
|
|
113
|
+
xmlhttp.open("GET", this.apiLocation + "/sim/dataset/" + this.id);
|
|
114
|
+
xmlhttp.onreadystatechange = () => {
|
|
115
|
+
if (xmlhttp.readyState === 4) {
|
|
116
|
+
if (xmlhttp.status === 200) {
|
|
117
|
+
const datasetInfo = JSON.parse(xmlhttp.responseText);
|
|
118
|
+
|
|
119
|
+
this.name = datasetInfo.name;
|
|
120
|
+
this.uuid = (datasetInfo.study !== undefined)?datasetInfo.study.uuid:undefined;
|
|
121
|
+
}
|
|
91
122
|
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
123
|
+
};
|
|
124
|
+
xmlhttp.send();
|
|
125
|
+
}
|
|
95
126
|
|
|
96
127
|
return {
|
|
97
128
|
errorMessage: "",
|
|
129
|
+
fileManager: undefined,
|
|
98
130
|
hasFinalisedUi: false,
|
|
99
131
|
hasValidSimulationUiInfo: false,
|
|
132
|
+
instance: undefined,
|
|
100
133
|
isMounted: false,
|
|
101
134
|
isSimulationValid: true,
|
|
102
135
|
layout: [],
|
|
103
|
-
|
|
136
|
+
libopencor: undefined,
|
|
137
|
+
name: null,
|
|
138
|
+
opencorBasedSimulation: true,
|
|
139
|
+
output: undefined,
|
|
104
140
|
perfectScollbarOptions: {
|
|
105
141
|
suppressScrollX: true,
|
|
106
142
|
},
|
|
143
|
+
pmrBasedCombineArchive: false,
|
|
107
144
|
showUserMessage: false,
|
|
108
|
-
|
|
109
|
-
|
|
145
|
+
simulationResults: {},
|
|
146
|
+
simulationResultsId: {},
|
|
110
147
|
simulationUiInfo: {},
|
|
148
|
+
solver: undefined,
|
|
111
149
|
userMessage: "",
|
|
112
150
|
ui: null,
|
|
113
|
-
uuid:
|
|
151
|
+
uuid: null,
|
|
114
152
|
};
|
|
115
153
|
},
|
|
116
154
|
methods: {
|
|
155
|
+
/**
|
|
156
|
+
* @vuese
|
|
157
|
+
* Generate the metadata associated with the plot which `index` is given.
|
|
158
|
+
* @arg `index`
|
|
159
|
+
*/
|
|
117
160
|
plotMetadata(index) {
|
|
118
161
|
return {
|
|
119
162
|
version: "1.1.0",
|
|
@@ -124,22 +167,262 @@ export default {
|
|
|
124
167
|
},
|
|
125
168
|
};
|
|
126
169
|
},
|
|
127
|
-
|
|
170
|
+
/**
|
|
171
|
+
* @vuese
|
|
172
|
+
* Download the PMR file associated with the given `url`.
|
|
173
|
+
* @arg `url`
|
|
174
|
+
*/
|
|
175
|
+
downloadPmrFile(url) {
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const xmlhttp = new XMLHttpRequest();
|
|
178
|
+
|
|
179
|
+
xmlhttp.open("POST", this.apiLocation + "/pmr_file");
|
|
180
|
+
xmlhttp.setRequestHeader("Content-type", "application/json");
|
|
181
|
+
xmlhttp.onreadystatechange = () => {
|
|
182
|
+
if (xmlhttp.readyState === 4) {
|
|
183
|
+
if (xmlhttp.status === 200) {
|
|
184
|
+
resolve(Uint8Array.from(atob(xmlhttp.response), (c) => c.charCodeAt(0)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
reject();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
xmlhttp.send(JSON.stringify({path: url.replace(PMR_URL, "")}));
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* @vuese
|
|
195
|
+
* Manage the file associated with the given `url` and `fileContents`.
|
|
196
|
+
* @arg `url`
|
|
197
|
+
* @arg `fileContents`
|
|
198
|
+
*/
|
|
199
|
+
manageFile(url, fileContents) {
|
|
200
|
+
let file = toRaw(this.fileManager).file(url);
|
|
201
|
+
|
|
202
|
+
if (file === null) {
|
|
203
|
+
file = new this.libopencor.File(url);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const fileContentsPtr = this.libopencor._malloc(fileContents.length);
|
|
207
|
+
const mem = new Uint8Array(this.libopencor.HEAPU8.buffer, fileContentsPtr, fileContents.length);
|
|
208
|
+
|
|
209
|
+
mem.set(fileContents);
|
|
210
|
+
|
|
211
|
+
file.setContents(fileContentsPtr, fileContents.length);
|
|
212
|
+
|
|
213
|
+
this.libopencor._free(fileContentsPtr);
|
|
214
|
+
|
|
215
|
+
return file;
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* @vuese
|
|
219
|
+
* Run the simulation using libOpenCOR.
|
|
220
|
+
*/
|
|
221
|
+
runSimulation() {
|
|
222
|
+
if (this.instance === undefined) {
|
|
223
|
+
const fileNameOrUrl = this.pmrBasedCombineArchive ? PMR_URL + this.id : this.simulationUiInfo.simulation.opencor.resource;
|
|
224
|
+
const document = new this.libopencor.SedDocument(toRaw(this.fileManager).file(fileNameOrUrl));
|
|
225
|
+
|
|
226
|
+
// Customise the ending point and point interval of the simulation, if
|
|
227
|
+
// needed.
|
|
228
|
+
|
|
229
|
+
if ( !this.pmrBasedCombineArchive
|
|
230
|
+
&& (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
|
|
231
|
+
&& (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
|
|
232
|
+
const simulation = document.simulations().get(0);
|
|
233
|
+
|
|
234
|
+
simulation.setOutputEndTime(this.simulationUiInfo.simulation.opencor.endingPoint);
|
|
235
|
+
simulation.setNumberOfSteps(this.simulationUiInfo.simulation.opencor.endingPoint / this.simulationUiInfo.simulation.opencor.pointInterval);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Retrieve an instance of the model.
|
|
239
|
+
|
|
240
|
+
this.instance = document.instantiate();
|
|
241
|
+
|
|
242
|
+
document.delete();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Run the simulation after passing some initial conditions to it, if any.
|
|
246
|
+
|
|
247
|
+
const instance = toRaw(this.instance);
|
|
248
|
+
|
|
249
|
+
instance.removeAllInitialConditions();
|
|
250
|
+
|
|
251
|
+
for (const [parameter, value] of Object.entries(this.parametersData())) {
|
|
252
|
+
instance.addInitialCondition(parameter, value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
instance.run();
|
|
256
|
+
|
|
257
|
+
// Retrieve the simulation results.
|
|
258
|
+
|
|
259
|
+
const res = {};
|
|
260
|
+
const instanceTask = instance.tasks().get(0);
|
|
261
|
+
|
|
262
|
+
for (const output of this.outputData()) {
|
|
263
|
+
if (output === instanceTask.voiName()) {
|
|
264
|
+
res[output] = instanceTask.voiAsArray();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (res[output] === undefined) {
|
|
268
|
+
for (let i = 0; i < instanceTask.stateCount(); ++i) {
|
|
269
|
+
if (output === instanceTask.stateName(i)) {
|
|
270
|
+
res[output] = instanceTask.stateAsArray(i);
|
|
271
|
+
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (res[output] === undefined) {
|
|
278
|
+
for (let i = 0; instanceTask.variableCount(); ++i) {
|
|
279
|
+
if (output === instanceTask.variableName(i)) {
|
|
280
|
+
res[output] = instanceTask.variableAsArray(i);
|
|
281
|
+
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.processSimulationResults(res);
|
|
289
|
+
|
|
290
|
+
this.showUserMessage = false;
|
|
291
|
+
},
|
|
292
|
+
/**
|
|
293
|
+
* @vuese
|
|
294
|
+
* Build the simulation UI using `simulationUiInfo`, a JSON object that describes the contents of the simulation UI.
|
|
295
|
+
* @arg `simulationUiInfo`
|
|
296
|
+
*/
|
|
297
|
+
buildSimulationUi(simulationUiInfo) {
|
|
128
298
|
// Keep track of the simulation UI information.
|
|
129
299
|
|
|
130
300
|
this.simulationUiInfo = simulationUiInfo;
|
|
131
301
|
|
|
132
302
|
// Make sure that the simulation UI information is valid.
|
|
133
303
|
|
|
134
|
-
this.hasValidSimulationUiInfo = validJson(this.simulationUiInfo);
|
|
304
|
+
this.hasValidSimulationUiInfo = validJson(this.simulationUiInfo, !this.pmrBasedCombineArchive);
|
|
135
305
|
|
|
136
306
|
if (!this.hasValidSimulationUiInfo) {
|
|
137
307
|
return;
|
|
138
308
|
}
|
|
139
309
|
|
|
310
|
+
// Retrieve and keep track of the solver to be used for the simulation, if
|
|
311
|
+
// needed.
|
|
312
|
+
|
|
313
|
+
if (!this.pmrBasedCombineArchive) {
|
|
314
|
+
this.simulationUiInfo.simulation.solvers.forEach((solver) => {
|
|
315
|
+
if ((solver.if === undefined) || evaluateValue(this, solver.if)) {
|
|
316
|
+
this.solver = solver;
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (this.solver === undefined) {
|
|
321
|
+
console.warn("SIMULATION: no solver name and/or solver version specified.");
|
|
322
|
+
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.opencorBasedSimulation = this.solver.name === OPENCOR_SOLVER_NAME;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Run the model if we are dealing with a PMR-based COMBINE archive or
|
|
330
|
+
// load libOpenCOR if we are dealing with an OpenCOR-based simulation and
|
|
331
|
+
// we want to use libOpenCOR.
|
|
332
|
+
|
|
333
|
+
if (this.pmrBasedCombineArchive) {
|
|
334
|
+
this.userMessage = "Running the model...";
|
|
335
|
+
this.showUserMessage = true;
|
|
336
|
+
|
|
337
|
+
this.$nextTick(() => {
|
|
338
|
+
this.runSimulation();
|
|
339
|
+
});
|
|
340
|
+
} else if (this.opencorBasedSimulation && (this.preferredSolver === LIBOPENCOR_SOLVER)) {
|
|
341
|
+
this.userMessage = "Retrieving and running the model...";
|
|
342
|
+
this.showUserMessage = true;
|
|
343
|
+
|
|
344
|
+
this.$nextTick(() => {
|
|
345
|
+
libOpenCOR().then((libopencor) => {
|
|
346
|
+
// Keep track of the libOpenCOR module and its file manager.
|
|
347
|
+
|
|
348
|
+
this.libopencor = libopencor;
|
|
349
|
+
this.fileManager = this.libopencor.FileManager.instance();
|
|
350
|
+
|
|
351
|
+
// Retrieve the model file, if needed.
|
|
352
|
+
|
|
353
|
+
const modelUrl = this.simulationUiInfo.simulation.opencor.resource;
|
|
354
|
+
|
|
355
|
+
this.downloadPmrFile(modelUrl).then((fileContents) => {
|
|
356
|
+
const file = this.manageFile(modelUrl, fileContents);
|
|
357
|
+
|
|
358
|
+
// In the case of a SED-ML file, we also need to retrieve its
|
|
359
|
+
// corresponding CellML file.
|
|
360
|
+
|
|
361
|
+
if (file.type().value === this.libopencor.File.Type.SEDML_FILE.value) {
|
|
362
|
+
const document = new this.libopencor.SedDocument(file);
|
|
363
|
+
const cellmlUrl = document.models().get(0).file().url();
|
|
364
|
+
|
|
365
|
+
this.downloadPmrFile(cellmlUrl).then((cellmlFileContents) => {
|
|
366
|
+
this.manageFile(cellmlUrl, cellmlFileContents);
|
|
367
|
+
|
|
368
|
+
this.runSimulation();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
document.delete();
|
|
372
|
+
} else {
|
|
373
|
+
this.runSimulation();
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
140
380
|
// Initialise our UI.
|
|
141
381
|
|
|
142
|
-
|
|
382
|
+
this.simulationUiInfo.output.data.forEach((data) => {
|
|
383
|
+
this.simulationResultsId[data.id] = data.name;
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
let index = -1;
|
|
387
|
+
|
|
388
|
+
this.simulationUiInfo.output.plots.forEach((outputPlot) => {
|
|
389
|
+
++index;
|
|
390
|
+
|
|
391
|
+
this.layout[index] = {
|
|
392
|
+
paper_bgcolor: "rgba(0, 0, 0, 0)",
|
|
393
|
+
plot_bgcolor: "rgba(0, 0, 0, 0)",
|
|
394
|
+
autosize: true,
|
|
395
|
+
margin: {
|
|
396
|
+
t: 25,
|
|
397
|
+
l: 55,
|
|
398
|
+
r: 25,
|
|
399
|
+
b: 30,
|
|
400
|
+
pad: 4,
|
|
401
|
+
},
|
|
402
|
+
loading: false,
|
|
403
|
+
options: {
|
|
404
|
+
responsive: true,
|
|
405
|
+
scrollZoom: true,
|
|
406
|
+
},
|
|
407
|
+
dragmode: "pan",
|
|
408
|
+
xaxis: {
|
|
409
|
+
title: {
|
|
410
|
+
text: outputPlot.xAxisTitle,
|
|
411
|
+
font: {
|
|
412
|
+
size: 10,
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
yaxis: {
|
|
417
|
+
title: {
|
|
418
|
+
text: outputPlot.yAxisTitle,
|
|
419
|
+
font: {
|
|
420
|
+
size: 10,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
});
|
|
143
426
|
|
|
144
427
|
// Finalise our UI.
|
|
145
428
|
// Note: we try both here and in the mounted() function since we have no
|
|
@@ -148,86 +431,117 @@ export default {
|
|
|
148
431
|
|
|
149
432
|
this.$nextTick(() => {
|
|
150
433
|
finaliseUi(this);
|
|
151
|
-
|
|
152
|
-
this.simulationData.forEach((data, index) => {
|
|
153
|
-
this.simulationData[index] = [{
|
|
154
|
-
x: [],
|
|
155
|
-
y: [],
|
|
156
|
-
type: "scatter",
|
|
157
|
-
}];
|
|
158
|
-
});
|
|
159
434
|
});
|
|
160
435
|
},
|
|
436
|
+
/**
|
|
437
|
+
* @vuese
|
|
438
|
+
* Run the simulation-based dataset directly on oSPARC. Not all simulation-based datasets can be run directly on
|
|
439
|
+
* oSPARC, but for those that can the simulation UI shows a `Run on oSPARC` button which, when clicked, calls this
|
|
440
|
+
* method.
|
|
441
|
+
*/
|
|
161
442
|
runOnOsparc() {
|
|
162
443
|
window.open(`https://osparc.io/study/${this.uuid}`, "_blank");
|
|
163
444
|
},
|
|
445
|
+
/**
|
|
446
|
+
* @vuese
|
|
447
|
+
* View the simulation-based dataset on the SPARC portal. The simulation UI has a `View Dataset` button which, when
|
|
448
|
+
* clicked, calls this method.
|
|
449
|
+
*/
|
|
164
450
|
viewDataset() {
|
|
165
451
|
window.open(`https://sparc.science/datasets/${this.id}?type=dataset`, "_blank");
|
|
166
452
|
},
|
|
167
|
-
|
|
168
|
-
|
|
453
|
+
/**
|
|
454
|
+
* @vuese
|
|
455
|
+
* View the simulation-based dataset on PMR. The simulation UI has a `View Workspace` button which, when clicked,
|
|
456
|
+
* calls this method.
|
|
457
|
+
*/
|
|
458
|
+
viewWorkspace() {
|
|
459
|
+
const url = PMR_URL + this.id;
|
|
460
|
+
|
|
461
|
+
window.open(url.substring(0, url.lastIndexOf("/")), "_blank");
|
|
462
|
+
},
|
|
463
|
+
/**
|
|
464
|
+
* @vuese
|
|
465
|
+
* Data needed to set a model's parameters.
|
|
466
|
+
*/
|
|
467
|
+
parametersData() {
|
|
468
|
+
const res = {};
|
|
469
|
+
|
|
470
|
+
this.simulationUiInfo.parameters.forEach((parameter) => {
|
|
471
|
+
res[parameter.name] = evaluateValue(this, parameter.value);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
return res;
|
|
475
|
+
},
|
|
476
|
+
/**
|
|
477
|
+
* @vuese
|
|
478
|
+
* Data needed to specify the model output.
|
|
479
|
+
*/
|
|
480
|
+
outputData() {
|
|
481
|
+
if (this.output === undefined) {
|
|
482
|
+
if (this.simulationUiInfo.output.data !== undefined) {
|
|
483
|
+
this.output = [];
|
|
484
|
+
|
|
485
|
+
this.simulationUiInfo.output.data.forEach((output) => {
|
|
486
|
+
this.output.push(output.name);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
169
490
|
|
|
170
|
-
|
|
491
|
+
return this.output;
|
|
492
|
+
},
|
|
493
|
+
/**
|
|
494
|
+
* @vuese
|
|
495
|
+
* Create the `request` that is going to be used by `startSimulation` to ask oSPARC to start the simulation.
|
|
496
|
+
*/
|
|
497
|
+
retrieveRequest() {
|
|
498
|
+
const request = {
|
|
499
|
+
solver: this.solver
|
|
500
|
+
};
|
|
171
501
|
|
|
172
|
-
if (
|
|
502
|
+
if (this.opencorBasedSimulation) {
|
|
173
503
|
request.opencor = {
|
|
174
504
|
model_url: this.simulationUiInfo.simulation.opencor.resource,
|
|
175
505
|
json_config: {},
|
|
176
506
|
};
|
|
177
|
-
} else {
|
|
178
|
-
request.osparc = {};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Specify the ending point and point interval, if we have some.
|
|
182
|
-
|
|
183
|
-
if ( isOpencorSimulation
|
|
184
|
-
&& (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
|
|
185
|
-
&& (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
|
|
186
|
-
request.opencor.json_config.simulation = {
|
|
187
|
-
"Ending point": this.simulationUiInfo.simulation.opencor.endingPoint,
|
|
188
|
-
"Point interval": this.simulationUiInfo.simulation.opencor.pointInterval,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Specify the parameters, if any.
|
|
193
|
-
|
|
194
|
-
if (this.simulationUiInfo.parameters !== undefined) {
|
|
195
|
-
let parameters = {};
|
|
196
507
|
|
|
197
|
-
this.simulationUiInfo.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
} else {
|
|
204
|
-
request.osparc.job_inputs = parameters;
|
|
508
|
+
if ( (this.simulationUiInfo.simulation.opencor.endingPoint !== undefined)
|
|
509
|
+
&& (this.simulationUiInfo.simulation.opencor.pointInterval !== undefined)) {
|
|
510
|
+
request.opencor.json_config.simulation = {
|
|
511
|
+
"Ending point": this.simulationUiInfo.simulation.opencor.endingPoint,
|
|
512
|
+
"Point interval": this.simulationUiInfo.simulation.opencor.pointInterval,
|
|
513
|
+
};
|
|
205
514
|
}
|
|
206
|
-
}
|
|
207
515
|
|
|
208
|
-
|
|
516
|
+
request.opencor.json_config.parameters = this.parametersData();
|
|
209
517
|
|
|
210
|
-
|
|
211
|
-
let index = -1;
|
|
518
|
+
const output = this.outputData();
|
|
212
519
|
|
|
213
|
-
|
|
520
|
+
if (output !== undefined) {
|
|
521
|
+
request.opencor.json_config.output = output;
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
request.osparc = {};
|
|
214
525
|
|
|
215
|
-
|
|
216
|
-
request.opencor.json_config.output[++index] = outputData.name;
|
|
217
|
-
});
|
|
526
|
+
request.osparc.job_inputs = this.parametersData();
|
|
218
527
|
}
|
|
219
528
|
|
|
220
529
|
return request;
|
|
221
530
|
},
|
|
531
|
+
/**
|
|
532
|
+
* @vuese
|
|
533
|
+
* Process the simulation results retrieved by `checkSimulation`. The simulation results are post-processed, if
|
|
534
|
+
* needed, and then readied for use by `PlotVuer`.
|
|
535
|
+
* @arg `results`
|
|
536
|
+
*/
|
|
222
537
|
processSimulationResults(results) {
|
|
223
538
|
// Convert, if needed, the results to a JSON format that is compatible
|
|
224
539
|
// with our OpenCOR results.
|
|
225
540
|
|
|
226
541
|
if (typeof(results) === "string") {
|
|
227
542
|
const SPACES = /[ \t]+/g;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
let iMax = lines[0].trim().split(SPACES).length;
|
|
543
|
+
const lines = results.trim().split("\n");
|
|
544
|
+
const iMax = lines[0].trim().split(SPACES).length;
|
|
231
545
|
|
|
232
546
|
results = {};
|
|
233
547
|
|
|
@@ -241,7 +555,7 @@ export default {
|
|
|
241
555
|
++i;
|
|
242
556
|
|
|
243
557
|
let j = -1;
|
|
244
|
-
|
|
558
|
+
const values = line.trim().split(SPACES);
|
|
245
559
|
|
|
246
560
|
values.forEach((value) => {
|
|
247
561
|
results[++j][i] = Number(value);
|
|
@@ -251,33 +565,37 @@ export default {
|
|
|
251
565
|
|
|
252
566
|
// Get the results ready for plotting.
|
|
253
567
|
|
|
254
|
-
|
|
255
|
-
let iMax = results[this.simulationDataId[Object.keys(this.simulationDataId)[0]]].length;
|
|
568
|
+
const parser = new math.parser();
|
|
256
569
|
|
|
257
|
-
this.
|
|
258
|
-
|
|
259
|
-
|
|
570
|
+
Object.keys(this.simulationResultsId).forEach((id) => {
|
|
571
|
+
parser.set(id, results[this.simulationResultsId[id]]);
|
|
572
|
+
});
|
|
260
573
|
|
|
261
|
-
|
|
262
|
-
xValue[i] = evaluateSimulationValue(this, results, outputPlot.xValue, i);
|
|
263
|
-
yValue[i] = evaluateSimulationValue(this, results, outputPlot.yValue, i);
|
|
264
|
-
}
|
|
574
|
+
let index = -1;
|
|
265
575
|
|
|
266
|
-
|
|
576
|
+
this.simulationUiInfo.output.plots.forEach((outputPlot) => {
|
|
577
|
+
this.simulationResults[++index] = [
|
|
267
578
|
{
|
|
268
|
-
x: xValue,
|
|
269
|
-
y: yValue,
|
|
579
|
+
x: parser.evaluate(outputPlot.xValue),
|
|
580
|
+
y: parser.evaluate(outputPlot.yValue),
|
|
270
581
|
type: "scatter",
|
|
271
582
|
},
|
|
272
583
|
];
|
|
273
584
|
});
|
|
274
585
|
},
|
|
586
|
+
/**
|
|
587
|
+
* @vuese
|
|
588
|
+
* Check the progress of the simulation using the given `data`, a JSON object that contains the simulation job ID,
|
|
589
|
+
* as well as the solver name and version. This method is first called by `startSimulation` and then every second by
|
|
590
|
+
* itself until the simulation is finished.
|
|
591
|
+
* @arg `data`
|
|
592
|
+
*/
|
|
275
593
|
checkSimulation(data) {
|
|
276
594
|
// Check the simulation.
|
|
277
595
|
|
|
278
|
-
|
|
596
|
+
const xmlhttp = new XMLHttpRequest();
|
|
279
597
|
|
|
280
|
-
xmlhttp.open("POST", this.apiLocation + "/check_simulation"
|
|
598
|
+
xmlhttp.open("POST", this.apiLocation + "/check_simulation");
|
|
281
599
|
xmlhttp.setRequestHeader("Content-type", "application/json");
|
|
282
600
|
xmlhttp.onreadystatechange = () => {
|
|
283
601
|
if (xmlhttp.readyState === 4) {
|
|
@@ -316,37 +634,24 @@ export default {
|
|
|
316
634
|
};
|
|
317
635
|
xmlhttp.send(JSON.stringify(data));
|
|
318
636
|
},
|
|
637
|
+
/**
|
|
638
|
+
* @vuese
|
|
639
|
+
* Start the simulation associated with the simulation-based dataset. The simulation UI has a `Run Simulation`
|
|
640
|
+
* button which, when clicked, calls this method.
|
|
641
|
+
*/
|
|
319
642
|
startSimulation() {
|
|
320
|
-
// Retrieve the solver to be used for the simulation.
|
|
321
|
-
|
|
322
|
-
let solver = undefined;
|
|
323
|
-
|
|
324
|
-
this.simulationUiInfo.simulation.solvers.forEach((crtSolver) => {
|
|
325
|
-
if ((crtSolver.if === undefined) || evaluateValue(this, crtSolver.if)) {
|
|
326
|
-
solver = crtSolver;
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
if (solver === undefined) {
|
|
331
|
-
console.warn("SIMULATION: no solver name and/or solver version specified.");
|
|
332
|
-
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
643
|
// Start the simulation (after resetting our previous simulation data, in
|
|
337
644
|
// case there were sonme).
|
|
338
|
-
// Note: we use this.$nextTick() so that the user message is shown before
|
|
339
|
-
// we get to post our HTTP request.
|
|
340
645
|
|
|
341
646
|
this.userMessage = "Loading simulation results...";
|
|
342
647
|
this.showUserMessage = true;
|
|
343
648
|
|
|
344
649
|
this.$nextTick(() => {
|
|
345
|
-
this.
|
|
650
|
+
this.simulationResults = {};
|
|
346
651
|
|
|
347
|
-
|
|
652
|
+
const xmlhttp = new XMLHttpRequest();
|
|
348
653
|
|
|
349
|
-
xmlhttp.open("POST", this.apiLocation + "/start_simulation"
|
|
654
|
+
xmlhttp.open("POST", this.apiLocation + "/start_simulation");
|
|
350
655
|
xmlhttp.setRequestHeader("Content-type", "application/json");
|
|
351
656
|
xmlhttp.onreadystatechange = () => {
|
|
352
657
|
if (xmlhttp.readyState === 4) {
|
|
@@ -368,39 +673,71 @@ export default {
|
|
|
368
673
|
}
|
|
369
674
|
}
|
|
370
675
|
};
|
|
371
|
-
xmlhttp.send(JSON.stringify(this.retrieveRequest(
|
|
372
|
-
solver: solver
|
|
373
|
-
})));
|
|
676
|
+
xmlhttp.send(JSON.stringify(this.retrieveRequest()));
|
|
374
677
|
});
|
|
375
678
|
},
|
|
376
679
|
},
|
|
377
680
|
created: function() {
|
|
378
|
-
// Try to retrieve the UI information
|
|
681
|
+
// Try to retrieve the UI information.
|
|
379
682
|
|
|
380
|
-
if (this.
|
|
683
|
+
if (this.id > 0) {
|
|
381
684
|
this.userMessage = "Retrieving UI information...";
|
|
382
685
|
this.showUserMessage = true;
|
|
383
686
|
|
|
384
687
|
// Retrieve and build the simulation UI.
|
|
385
|
-
// Note: we use this.$nextTick() so that the user message is shown before
|
|
386
|
-
// we get to post our HTTP request.
|
|
387
688
|
|
|
388
689
|
this.$nextTick(() => {
|
|
389
|
-
|
|
690
|
+
const xmlhttp = new XMLHttpRequest();
|
|
390
691
|
|
|
391
|
-
xmlhttp.open("GET", this.apiLocation + "/simulation_ui_file/" + this.id
|
|
392
|
-
xmlhttp.setRequestHeader("Content-type", "application/json");
|
|
692
|
+
xmlhttp.open("GET", this.apiLocation + "/simulation_ui_file/" + this.id);
|
|
393
693
|
xmlhttp.onreadystatechange = () => {
|
|
394
694
|
if (xmlhttp.readyState === 4) {
|
|
395
695
|
this.showUserMessage = false;
|
|
396
696
|
|
|
397
697
|
if (xmlhttp.status === 200) {
|
|
398
|
-
this
|
|
698
|
+
this.$nextTick(() => {
|
|
699
|
+
this.buildSimulationUi(JSON.parse(xmlhttp.responseText));
|
|
700
|
+
});
|
|
399
701
|
}
|
|
400
702
|
}
|
|
401
703
|
};
|
|
402
704
|
xmlhttp.send();
|
|
403
705
|
});
|
|
706
|
+
} else if (this.id !== 0) {
|
|
707
|
+
this.userMessage = "Retrieving OMEX file...";
|
|
708
|
+
this.showUserMessage = true;
|
|
709
|
+
|
|
710
|
+
// Retrieve the OMEX file, extract the simulation UI JSON file from it and
|
|
711
|
+
// then build the simulation UI.
|
|
712
|
+
|
|
713
|
+
this.$nextTick(() => {
|
|
714
|
+
const xmlhttp = new XMLHttpRequest();
|
|
715
|
+
|
|
716
|
+
xmlhttp.open("POST", this.apiLocation + "/pmr_file");
|
|
717
|
+
xmlhttp.setRequestHeader("Content-type", "application/json");
|
|
718
|
+
xmlhttp.onreadystatechange = () => {
|
|
719
|
+
if ((xmlhttp.readyState === 4) && (xmlhttp.status === 200)) {
|
|
720
|
+
libOpenCOR().then((libopencor) => {
|
|
721
|
+
this.pmrBasedCombineArchive = true;
|
|
722
|
+
this.libopencor = libopencor;
|
|
723
|
+
this.fileManager = this.libopencor.FileManager.instance();
|
|
724
|
+
|
|
725
|
+
const fileContents = Uint8Array.from(atob(xmlhttp.response), (c) => c.charCodeAt(0));
|
|
726
|
+
const file = this.manageFile(PMR_URL + this.id, fileContents);
|
|
727
|
+
|
|
728
|
+
const decoder = new TextDecoder();
|
|
729
|
+
const simulationUiInfo = JSON.parse(decoder.decode(file.childFile("simulation.json").contents()));
|
|
730
|
+
|
|
731
|
+
this.showUserMessage = false;
|
|
732
|
+
|
|
733
|
+
this.$nextTick(() => {
|
|
734
|
+
this.buildSimulationUi(simulationUiInfo);
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
xmlhttp.send(JSON.stringify({path: this.id}));
|
|
740
|
+
});
|
|
404
741
|
}
|
|
405
742
|
},
|
|
406
743
|
mounted: function() {
|