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: