Source: molecularformulae.js

/**
 * Code to calculate distribution based on molecular formula
 */
class MolecularFormulae{
	constructor(){
		this.averagine = "C4.9384H7.7583N1.3577O1.4773S0.0417" ;
		this.avrgMass = 111.0543055992;
		this.atomList = ["C","H","N","O","S"]
		this.carbonCnt = 4.9384;
		this.hydrogenCnt = 7.7583;
		this.nitrogenCnt = 1.3577;
		this.oxygenCnt = 1.4773
		this.sulphurCnt = 0.0417;
		this.minintensity = 0.0001;
		this.protonMass = 1.00727647  ;
		this.eachAtomCount;
		this.toleraceMassDiff = 0.01;
		this.mininte = 1;
		this.intensityTolerance = 1 ;
	}
	/**
	 * Function to calculate the emass distrubution fo the given sequence
	 * @param {Double} mass - Contains the mass at that position of the sequence
	 * @param {Array} peakDataList - Contains the peak list provided by the user
	 * @param {Float} charge - Contains the chrage of the ion
	 */
	emass(mass,charge,peakDataList){
		//Give the count of each atom in molecule
		this.eachAtomCount = mass/this.avrgMass ;
		let calculatedMass;
		let atomCountList ;
		[atomCountList,calculatedMass] = this.getMolecularFormula() ;
		let massError = mass - calculatedMass ;
		let len = atomCountList.length;
		let totDistributionList = [] ;
		let numOfAtoms = this.atomList.length ;
		for(let i=0;i<len;i++)
		{
			for(let j=0;j<atomCountList[i].count;j++)
			{
				let atomdist = getIsotopicMassRef(atomCountList[i].atom);
				totDistributionList = this.getMassAndIntensity(totDistributionList,atomdist);
			}
		}
		for(let k=0;k<totDistributionList.length;k++)
		{
			for(let i = 0; i < numOfAtoms ; i++)
			{
				let IsotopicMass = getIsotopicMassOfAtom(atomCountList[i].atom);
				totDistributionList[k].mass = totDistributionList[k].mass + (IsotopicMass[0].mass * atomCountList[i].count) ;
			}
		}
		totDistributionList = this.getMZwithHighInte(totDistributionList,charge,massError,peakDataList);
		totDistributionList = this.getNormalizedIntensity(totDistributionList,peakDataList);
		return totDistributionList ;
	}
	/**
	 * Logic to calculate distribution 
	 * @param {Array} totDistributionList - Array with current total distribution
	 * @param {Array} aminoAcidDist - Array with existing calculated distribution of amino acid 
	 */
	getMassAndIntensity(totDistributionList,atomdist){
		let maxintensity = 0 ;
		if(totDistributionList.length == 0)
		{
			return atomdist ;
		}
		else
		{
			let len = totDistributionList.length + atomdist.length - 1;
	  		let completeDistributionList = new Array(len).fill(0) ;
	  		for(let i=0;i<totDistributionList.length;i++)
			{
				for(let j=0;j<atomdist.length;j++)
				{
					let intensity = 0;
					let index = i+j ;
					let mass = totDistributionList[i].mass+atomdist[j].mass ;
					intensity = totDistributionList[i].intensity * atomdist[j].intensity ;
					if(completeDistributionList[index] != 0) intensity = intensity + completeDistributionList[index].intensity ;
					completeDistributionList[index] = {mass:mass,intensity:intensity};
					if(intensity > maxintensity) maxintensity = intensity ;
				}
			}
	  		let completeDistributionList_temp = [];
			for(let i = 0; i<completeDistributionList.length; i++)
			{
				let tempintensityVal = (completeDistributionList[i].intensity/maxintensity)*100;
				if( tempintensityVal > this.minintensity)
				{
					completeDistributionList[i].intensity = Math.round(tempintensityVal * 1000000) / 1000000;//tempintensityVal//Math.round(tempintensityVal * 1000000) / 1000000; //parseFloat(((tempDistributionList[i].intensity/maxintensity)*100).toFixed(6)) ;
					completeDistributionList_temp.push(completeDistributionList[i]);
				}
			}
			completeDistributionList = completeDistributionList_temp ;
			return completeDistributionList ;
		}
	}
	/**
	 * Code to get the molecular formular based on the mass.
	 * Number of atoms of each atom in the averagine * number of
	 * atoms of each atom calculated from mass gives the total number of atoms.
	 */
	getMolecularFormula(){
		let atomListwithCnt = new Array(5) ;
		let len = this.atomList.length;
		let mass = 0;
		for(let i=0;i<len;i++)
		{
			let count ;
			if(this.atomList[i] == "C"){
				count = parseInt(this.eachAtomCount * this.carbonCnt) ;
				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
			}
			else if(this.atomList[i] == "H"){
				count = parseInt(this.eachAtomCount * this.hydrogenCnt) ;
				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
			}
			else if(this.atomList[i] == "N"){
				count = parseInt(this.eachAtomCount * this.nitrogenCnt) ;
				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
			}
			else if(this.atomList[i] == "O"){
				count = parseInt(this.eachAtomCount * this.oxygenCnt) ;
				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
			}
			else if(this.atomList[i] == "S"){
				count = parseInt(this.eachAtomCount * this.sulphurCnt) ;
				mass = mass + count*getIsotopicMassOfAtom(this.atomList[i])[0].mass;
			}
			atomListwithCnt[i] = {atom:this.atomList[i],count:count};
		}
		return [atomListwithCnt,mass] ;
	}
	/**
	 * Code to calculate MZ(mass/charge) and remove low intensity values
	 * @param {Array} totDistributionList - Array with total distribution
	 * @param {Float} charge - Float from mass list 
	 * @param {Float} massError - Calculated massError needed to be added 
	 * as we taken int values of number of atoms eleminating mass from the float values. 
	 * @param {Array} peakDataList - Array of peak list from user
	 */
	getMZwithHighInte(totDistributionList,charge,massError,peakDataList)
	{
		let len = totDistributionList.length;
		let newtotDistributionList = [];
		let overaAllMaxIntensity = 0 ;
		let onePerctInte = 0;
		let peakListLen = peakDataList.length;
		for(let k=0;k<peakListLen ; k++)
		{
			if(peakDataList[k].intensity > overaAllMaxIntensity) overaAllMaxIntensity = peakDataList[k].intensity ;
		}
		onePerctInte = overaAllMaxIntensity/100 ;
		for(let i=0;i<len;i++)
		{
			let intensity = totDistributionList[i].intensity ;
			intensity = overaAllMaxIntensity * intensity/100 ;
			if(intensity > onePerctInte)
			{
				let mz = (totDistributionList[i].mass+massError)/charge + this.protonMass;
				let intensity = totDistributionList[i].intensity ;
				//Converting mass variable to mz(mass/charge)
				let tempdistObj = {mz:mz,intensity:intensity};
				newtotDistributionList.push(tempdistObj);
			}
		}
		return newtotDistributionList ;
	}
	/**
	 * Code to normalize the Intensity. 
	 * Take the average of intensity from the peaks entered by the user.
	 * Take the average of the calculated distribution for each Array element in the Array. 
	 * Make both of them equal and calculating the rest of the 
	 * distribution intensity based on the avg value from the peak list.
	 * @param {Array} totDistributionList - Total distribution calculated
	 * @param {Array} peakDataList - Peak Data entered by the user
	 */
	getNormalizedIntensity(totDistributionList,peakDataList)
	{
		let len = totDistributionList.length;
		let peakListLen = peakDataList.length;
		let intensity = 0;
		let count = 0 ;
		let distributionInte = 0;
		let maxinte = 0;
		let mininte = 100;

		let peakMaxinte = 0;
		let peakMininte = 10000000;

		let maxMz = 0;
		let minMz = 10000000;
		for(let i=0;i<len;i++)
		{
			for(let j=0;j<peakListLen;j++)
			{
				if((totDistributionList[i].mz - peakDataList[j].mz) <= this.toleraceMassDiff
					&& (totDistributionList[i].mz - peakDataList[j].mz) >= 0-this.toleraceMassDiff )
				{
					if(maxMz < totDistributionList[i].mz){
						maxMz = totDistributionList[i].mz;
					}
					if(minMz > totDistributionList[i].mz){
						minMz = totDistributionList[i].mz;
					}
					count = count + 1;
				}
			}
		}
		maxMz = maxMz + this.toleraceMassDiff;
		minMz = minMz - this.toleraceMassDiff;
		for(let i=0;i<len;i++)
		{
			if(minMz <= totDistributionList[i].mz &&  totDistributionList[i].mz <= maxMz)
			{
				if(maxinte < totDistributionList[i].intensity){
					maxinte = totDistributionList[i].intensity;
				}
				if(mininte > totDistributionList[i].intensity){
					mininte = totDistributionList[i].intensity;
				}
			}
		}
		for(let j=0;j<peakListLen;j++)
		{
			if(peakDataList[j].mz >= minMz && peakDataList[j].mz <= maxMz)
			{
				if(peakMaxinte < peakDataList[j].intensity){
					peakMaxinte = peakDataList[j].intensity;
				}
				if(peakMininte > peakDataList[j].intensity){
					peakMininte = peakDataList[j].intensity;
				}
			}
		}
		if(count !=0 )
		{
			let avg ;
			let distributionAvgInte;

			avg = (peakMaxinte + peakMininte)/2;
			distributionAvgInte = (maxinte+mininte)/2;
			for(let i=0;i<len;i++)
			{
				totDistributionList[i].intensity = (avg * totDistributionList[i].intensity)/distributionAvgInte ;
			}
		}
		return totDistributionList ;
	}
}