Cheminfo-to-web Project

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:

(from OpenMD example files)

Input Data



Calculation Result