OpenMD Demo
Molecular dynamics calculation often requires a lot of time, from minutes to hours, even days. So OpenMD JS library should not be used in the main thread, or it will surely block the web browser UI. Web worker is the ideal circumstance to run OpenMD calculation while data can be transferred between web worker and main thread by messages.
In the main thread JS file, a extra web worker should firstly be created:
var openMdWorker = new Worker('worker.js'); // load worker js file and create the OpenMD worker
var result = {}; // stores the calculation result
// Set message handler, response to messages from worker
openMdWorker.onmessage = function(e) {
var data = e.data;
if (data)
{
if (data.msgType === 'calculationResult') // worker reports that the calculation is done
{
if (data.successful) // calculation suceeded
{
result.report = data.report; // report data, same as the content of .report file generated by native OpenMD
result.eor = data.eor // eor data, same as the content of .eor file generated by native OpenMD
result.stat = data.stat; // status data, same as the content of .stat file generated by native OpenMD
result.dump = data.dump; // dump data, same as the content of .dump file generated by native OpenMD
}
else // calculation failed
{
setStatus('Calculation failed! Please change browser console for the details.');
}
}
else if (data.msgType === 'progress') // worker reports calculation progress
{
var percent = Math.round(data.percentage);
var remainingTime = data.remainingTime; // in sec
var endTime = new Date(Date.now() + remainingTime * 1000);
var sStatus = 'Calculating: ' + percent + '% done, estimate ending in ' + endTime.toLocaleString();
setStatus(sStatus);
}
else if (data.msgType === 'log') // show log messages from worker
{
console.log(data.msg);
}
else if (data.msgType === 'error') // show error messages from worker
{
console.error(data.msg);
}
}
}
In general, native OpenMD program receives a .omd input file while additional data files or force field files can be included in the main input file. In JavaScript aspect, those input data should first be prepared in main thread (e.g. loaded from local file through FileAPI or load from URL by AJAX), converted into string, then be passed to the web worker by message:
var incContents = [{'fileId': 'alkane.inc', 'content': '...'}]; // prepare include file data
var omdData = '...'; // set source OMD file data
// Transfer include data to worker
openMdWorker.postMessage({'msgType': 'incData', 'incData': JSON.stringify(incFileData)});
// Transfer source OMD data to worker and starts caluclation
openMdWorker.postMessage({'msgType': 'calculate', 'omdData': omdData});
Meanwhile, the worker.js should respond to messages from main thread, run the calcualtion job (by instance of wrapper class OpenMdRunner) and report status of calculation back to main thread:
importScripts('../../../libs/compiled/openMD.js'); // OpenMD JS lib should be loaded in the worker first
var Module = OpenMdModule(); // create OpenMD module object
var omdData, incData; // global variable to store source data transfered from main thread
onmessage = function(e) // message handler, receive messages from main thread
{
var data = e.data;
if (data)
{
if (data.msgType === 'incData') // set include file data
{
incData = JSON.parse(data.incData);
}
if (data.msgType === 'calculate') // set main data, begin calculation
{
omdData = data.omdData;
execRunner(); // run the calculation
}
}
}
function execRunner() // function to do the calculation
{
var runner = new Module.OpenMdRunner(); // create OpenMD calculation runner object
// prepare include data
if (incData && incData.length)
{
incData.forEach(function(item){
runner.addIncludeData(item.content, item.fileId);
});
}
// start calculation
var success = runner.runOmdJob(omdData);
// send calculation result back to main thread
var result = {'msgType': 'calculationResult', 'successful': success};
if (success)
{
result.report = runner.getReportData();
result.eor = runner.getEorData();
result.stat = runner.getStatData();
result.dump = runner.getDumpData();
}
postMessage(result); // post calculation result back to main thread
runner.delete(); // free runner object
close(); // close and destroy worker thread
}
Live demo: