package com.actelion.research.chem.phesa;

import com.actelion.research.calc.Matrix;
import com.actelion.research.chem.Coordinates;
import com.actelion.research.chem.StereoMolecule;
import com.actelion.research.chem.alignment3d.transformation.ExponentialMap;
import com.actelion.research.chem.alignment3d.transformation.Transformation;
import com.actelion.research.chem.conf.Conformer;
import com.actelion.research.chem.phesa.pharmacophore.IonizableGroupDetector;
import com.actelion.research.chem.phesa.pharmacophore.PharmacophoreCalculator;
import com.actelion.research.chem.phesa.pharmacophore.pp.IPharmacophorePoint;
import com.actelion.research.chem.phesa.pharmacophore.pp.PPGaussian;
import com.actelion.research.util.EncoderFloatingPointNumbers;

import java.util.ArrayList;
import java.util.List;

/**
 * @version: 1.0, February 2018
 * Author: J. Wahl
 * class to approximate the volume of a molecule as a sum of atom-centered Gaussians, as introduced by Grant and Pickup, J. Phys. Chem. 1995, 99, 3503-3510
 * no higher order terms (atom-atom overlaps) are calculated to reduce computational costs
*/
public class MolecularVolume extends ShapeVolume{
	static public final double p = 2.82842712475; // height of Atomic Gaussian, 2*sqrt(2), commonly used in the literature: Haque and Pande, DOI 10.1002/jcc.11307 
	static public final double alpha_pref = 2.41798793102; // taken from DOI 10.1002/jcc.11307

	private ArrayList<VolumeGaussian> volumeGaussians; //exclusion and inclusion spheres
	private ArrayList<Coordinates> hydrogens;

	public MolecularVolume(List<AtomicGaussian> atomicGaussiansInp,List<PPGaussian> ppGaussiansInp,List<VolumeGaussian> volGaussians, 
			List<Coordinates> hydrogenCoords) {
		this.atomicGaussians = new ArrayList<AtomicGaussian>();
		for(AtomicGaussian ag : atomicGaussiansInp) {
			atomicGaussians.add(new AtomicGaussian(ag));
		}
		this.ppGaussians = new ArrayList<PPGaussian>();
		for(PPGaussian pg : ppGaussiansInp) {
			ppGaussians.add(new PPGaussian(pg));
		}
		this.hydrogens = new ArrayList<Coordinates>();
		for(Coordinates hydrogen : hydrogenCoords) {
			this.hydrogens.add(hydrogen);
		}
		
		this.volumeGaussians = new ArrayList<VolumeGaussian>();
		for(VolumeGaussian eg : volGaussians) {
			this.volumeGaussians.add(new VolumeGaussian(eg));
		}

		this.calcCOM();
	}

	public void updateCOM() {
		this.calcCOM();
	}
	
	private void updateAtomIndeces(List<? extends Gaussian3D> gaussians,int[] map) {
		for(Gaussian3D gaussian:gaussians)
			gaussian.updateAtomIndeces(map);
	}
	
	public void updateAtomIndeces(int[] map) {
		updateAtomIndeces(ppGaussians,map);
		updateAtomIndeces(atomicGaussians,map);
		updateAtomIndeces(volumeGaussians,map);
	}

	public MolecularVolume(StereoMolecule mol) {
		this.hydrogens = new ArrayList<Coordinates>();
		this.volumeGaussians = new ArrayList<VolumeGaussian>();
		this.calc(mol);
		this.calcPPVolume(mol);
		this.calcCOM();
	}
	
	public MolecularVolume(MolecularVolume original, Conformer conf) {
		this(original);
		update(conf);
	}
	
	/**
	 * calculate the Overlap of the two molecular volumes as a function a transform vector that is applied to the query molecule
	 * overlap Volume of two molecular Volumes  formulated as a summed overlap of atomic Gaussians
	 * taken from Grant, Gallardo, Pickup, Journal of Computational Chemistry, 17, 1653-1666, 1996
	 * returns a double[2]: the first double is the total overlap, whereas the second value is the specific 
	 * contribution of additional volume gaussians (inclusion, exclusion)
	 * @param original
	 * @return
	 */

	public MolecularVolume(MolecularVolume original) {
		this.atomicGaussians = new ArrayList<AtomicGaussian>();
		this.ppGaussians = new ArrayList<PPGaussian>();
		this.volumeGaussians = new ArrayList<VolumeGaussian>();

		for(AtomicGaussian ag : original.getAtomicGaussians())
			this.atomicGaussians.add(new AtomicGaussian(ag));

		for(PPGaussian pg : original.getPPGaussians())
			this.ppGaussians.add(new PPGaussian(pg));

		this.hydrogens = new ArrayList<Coordinates>();
		for(Coordinates hydrogen : original.hydrogens)
			this.hydrogens.add(new Coordinates(hydrogen));

		for(VolumeGaussian eg : original.volumeGaussians)
			this.volumeGaussians.add(new VolumeGaussian(eg));

		this.com = new Coordinates(original.com);
	}
	

	/**
	 * calculate the self-overlap of the base molecule
	 * @return
	 */
	@Override
	public double getSelfAtomOverlap(){
			double Vtot = 0.0;
			for(AtomicGaussian at:atomicGaussians){
				for(AtomicGaussian at2:atomicGaussians){
					Vtot += at.getVolumeOverlap(at2);
				}
				for(VolumeGaussian vg : volumeGaussians) {
					if(vg.getRole()!=VolumeGaussian.INCLUSION)
						continue;
					Vtot += vg.getRole()*at.getVolumeOverlap(vg);
				}
			}
			
			for(VolumeGaussian vg:volumeGaussians){
				if(vg.getRole()!=VolumeGaussian.INCLUSION)
					continue;
				for(VolumeGaussian vg2 : volumeGaussians) {
					//only consider self-overlap of inclusion spheres
					if(vg2.getRole()!=VolumeGaussian.INCLUSION)
						continue;
					Vtot += vg2.getVolumeOverlap(vg);
				}
			}
	
		return Vtot;
	}
	
	public double[] getTotalAtomOverlap(double[] transform, MolecularVolume fitVol){
		double[] result = new double[2];
		ExponentialMap eMap = new ExponentialMap(transform[0],transform[1],transform[2]);
		double Vtot = 0.0;
		double Vvol = 0.0;
		Coordinates com = fitVol.getCOM();
		double[][] rotMatrix = eMap.toQuaternion().getRotMatrix().getArray();
		List<AtomicGaussian> fitGaussians = fitVol.atomicGaussians;
		Coordinates[] fitCenterModCoords = new Coordinates[fitGaussians.size()];
		for(int k=0;k<fitGaussians.size();k++) {
				Coordinates center = new Coordinates(fitGaussians.get(k).getCenter());
				center.sub(com);
			    center.rotate(rotMatrix);
			    center.add(com);
			    center.x += transform[3];
			    center.y += transform[4];
			    center.z += transform[5];
			    fitCenterModCoords[k] = center;
		}

		for(AtomicGaussian refAt:atomicGaussians){
			int index = 0;
			for(AtomicGaussian fitAt:fitVol.atomicGaussians){
				Vtot += refAt.getVolumeOverlap(fitAt, fitCenterModCoords[index],Gaussian3D.SQUARE_DIST_CUTOFF);
				index+=1;	
			}
		}
		
		for(VolumeGaussian refVol:volumeGaussians){
			int index = 0;
			for(AtomicGaussian fitAt:fitVol.atomicGaussians){
				double overlap = refVol.getRole()*refVol.getVolumeOverlap(fitAt, fitCenterModCoords[index],Gaussian3D.SQUARE_DIST_CUTOFF);
				Vtot += overlap;
				Vvol += overlap;
				index+=1;	
			}
		}
		if(Vtot<0)
			Vtot = 0.0;
		result[0] = Vtot;
		result[1] = Vvol;
		return result;
	}

	/**
	 * calculates the molecular Volume for a StereoMolecule with 3D coordinates
	 * @param mol
	 */
	private void calc(StereoMolecule mol) { 
		this.atomicGaussians = new ArrayList<AtomicGaussian>();
		int nrOfAtoms = mol.getAllAtoms();
		for (int i=0;i<nrOfAtoms;i++) {
			
			if(mol.getAtomicNo(i)==1){ //hydrogens don't contribute to the molecular volume
				this.hydrogens.add(new Coordinates(mol.getAtomCoordinates(i)));
				continue;
			}
			else if(mol.getAtomicNo(i)==0) {
				Coordinates coords = new Coordinates(mol.getAtomCoordinates(i));
				AtomicGaussian atomicGaussian = new AtomicGaussian(i,6,coords);
				atomicGaussian.setWeight(0.0);
				this.atomicGaussians.add(atomicGaussian);
			}
			else {
				Coordinates coords = new Coordinates(mol.getAtomCoordinates(i));
				AtomicGaussian atomicGaussian = new AtomicGaussian(i,mol.getAtomicNo(i),coords);
				this.atomicGaussians.add(atomicGaussian);
			}
		}
	}
	
	@Override
	protected void rotate180DegreeAroundAxis(PheSAAlignment.axis a) {
		super.rotate180DegreeAroundAxis(a);
		rotateGaussians180DegreeAroundAxis(volumeGaussians,a);
	}
	
	/**
	 * calculates the pharmacophore points and corresponding volumes of a 3d molecule
	 * @param mol
	 */
	
	private void calcPPVolume(StereoMolecule mol) {
		ppGaussians = new ArrayList<PPGaussian>();
		List<IPharmacophorePoint> ppPoints = new ArrayList<IPharmacophorePoint>();
		IonizableGroupDetector detector = new IonizableGroupDetector(mol);
		ppPoints.addAll(detector.detect());
		ppPoints.addAll(PharmacophoreCalculator.getPharmacophorePoints(mol));
		for(IPharmacophorePoint ppPoint : ppPoints )
			ppGaussians.add(new PPGaussian(6,ppPoint));
	}

	/**
	 * calculates volume weighted center of mass of the molecular Volume
	 */
	@Override
	public void calcCOM(){ 
		double volume = 0.0;
		double comX = 0.0;
		double comY = 0.0;
		double comZ = 0.0;
		for(AtomicGaussian atGauss : this.atomicGaussians){
			volume += atGauss.getVolume();
			comX += atGauss.getCenter().x*atGauss.getVolume();
			comY += atGauss.getCenter().y*atGauss.getVolume();
			comZ += atGauss.getCenter().z*atGauss.getVolume();
		}
		for(VolumeGaussian volGauss : this.volumeGaussians){
			volume += volGauss.getRole()*volGauss.getVolume();
			comX += volGauss.getRole()*volGauss.getCenter().x*volGauss.getVolume();
			comY += volGauss.getRole()*volGauss.getCenter().y*volGauss.getVolume();
			comZ += volGauss.getRole()*volGauss.getCenter().z*volGauss.getVolume();
		}

		comX = comX/volume;
		comY = comY/volume;
		comZ = comZ/volume;
		this.com = new Coordinates(comX,comY,comZ);
	}
	
	@Override
	public Matrix getCovarianceMatrix() {
		Matrix massMatrix = new Matrix(3,3); 
		double volume = 0.0;
		for (AtomicGaussian ag : atomicGaussians){
			volume += ag.getVolume();
			double value = ag.getVolume()*ag.getCenter().x*ag.getCenter().x;
			massMatrix.addToElement(0,0,value);
			value = ag.getVolume()*ag.getCenter().x*ag.getCenter().y;
			massMatrix.addToElement(0,1,value);
			value = ag.getVolume()*ag.getCenter().x*ag.getCenter().z;
			massMatrix.addToElement(0,2,value);
			value = ag.getVolume()*ag.getCenter().y*ag.getCenter().y;
			massMatrix.addToElement(1,1,value);
			value = ag.getVolume()*ag.getCenter().y*ag.getCenter().z;
			massMatrix.addToElement(1,2,value);
			value = ag.getVolume()*ag.getCenter().z*ag.getCenter().z;
			massMatrix.addToElement(2,2,value);	
		}
		for (VolumeGaussian vg : volumeGaussians){
			volume += vg.getRole()*vg.getVolume();
			double value = vg.getRole()*vg.getVolume()*vg.getCenter().x*vg.getCenter().x;
			massMatrix.addToElement(0,0,value);
			value = vg.getRole()*vg.getVolume()*vg.getCenter().x*vg.getCenter().y;
			massMatrix.addToElement(0,1,value);
			value = vg.getRole()*vg.getVolume()*vg.getCenter().x*vg.getCenter().z;
			massMatrix.addToElement(0,2,value);
			value = vg.getRole()*vg.getVolume()*vg.getCenter().y*vg.getCenter().y;
			massMatrix.addToElement(1,1,value);
			value = vg.getRole()*vg.getVolume()*vg.getCenter().y*vg.getCenter().z;
			massMatrix.addToElement(1,2,value);
			value = vg.getRole()*vg.getVolume()*vg.getCenter().z*vg.getCenter().z;
			massMatrix.addToElement(2,2,value);	
		}
		massMatrix.set(0,0,massMatrix.get(0,0)/volume);
		massMatrix.set(0,1,massMatrix.get(0,1)/volume);
		massMatrix.set(0,2,massMatrix.get(0,2)/volume);
		massMatrix.set(1,1,massMatrix.get(1,1)/volume);
		massMatrix.set(1,2,massMatrix.get(1,2)/volume);
		massMatrix.set(2,2,massMatrix.get(2,2)/volume);
		massMatrix.set(1,0,massMatrix.get(0,1));
		massMatrix.set(2,0,massMatrix.get(0,2));
		massMatrix.set(2,1,massMatrix.get(1,2));
		
		return massMatrix;
	}

	
	public ArrayList<VolumeGaussian> getVolumeGaussians() {
		return this.volumeGaussians;
	}
	
	@Override 
	protected void transformGaussians(Transformation transform) {
		super.transformGaussians(transform);
		transformGaussians(volumeGaussians,transform);
	}


	public ArrayList<Coordinates> getHydrogens() {
		return this.hydrogens;
	}
	
	private void updateHydrogens(StereoMolecule mol) {
		int h = 0;
		for(int i = mol.getAtoms();i<mol.getAllAtoms();i++) {
			hydrogens.get(h).set(new Coordinates(mol.getAtomCoordinates(i)));
			h++;
		}
	}
	
	private void updateHydrogens(Conformer conf) {
		int h = 0;
		for(int i = conf.getMolecule().getAtoms();i<conf.getMolecule().getAllAtoms();i++) {
			hydrogens.get(h).set(new Coordinates(conf.getCoordinates(i)));
			h++;
		}
			
	}
	
	@Override
	public void update(StereoMolecule mol) {
		updateCoordinates(getAtomicGaussians(),mol.getAtomCoordinates());
		updateCoordinates(getPPGaussians(),mol.getAtomCoordinates());
		updateCoordinates(getVolumeGaussians(),mol.getAtomCoordinates());
		updateHydrogens(mol);
	}
	
	@Override
	public void update(Conformer conf) {
		updateCoordinates(getAtomicGaussians(),conf.getCoordinates());
		updateCoordinates(getPPGaussians(),conf.getCoordinates());
		updateCoordinates(getVolumeGaussians(),conf.getCoordinates());
		updateHydrogens(conf);
	}
	public  Matrix createCanonicalOrientation(Conformer conf) {
		Matrix rotMat = super.createCanonicalOrientation(conf);
		for(VolumeGaussian vg : getVolumeGaussians())
			vg.rotateShift(rotMat);
		return rotMat;
	}

	@Override
	public void translateToCOM(Coordinates com) {
		for (AtomicGaussian ag : getAtomicGaussians())
			ag.getCenter().sub(com);  //translate atomicGaussians. Moves center of mass to the origin.

		for (PPGaussian pg : getPPGaussians())
			pg.getCenter().sub(com);  //translate atomicGaussians. Moves center of mass to the origin.

		for (VolumeGaussian vg : getVolumeGaussians())
			vg.translateRef(com.scaleC(-1.0));  //translate atomicGaussians. Moves center of mass to the origin.

		for (Coordinates hydrogen : getHydrogens())
			hydrogen.sub(com);  //translate atomicGaussians. Moves center of mass to the origin.

		calcCOM();
	}

	public String encodeFull() {
		StringBuilder molVolString = new StringBuilder();
		molVolString.append(Integer.toString(atomicGaussians.size()));
		molVolString.append("  ");
		for(AtomicGaussian ag : atomicGaussians) {
			molVolString.append(ag.encode());
			molVolString.append("  ");
		}

		molVolString.append(Integer.toString(ppGaussians.size()));
		
		molVolString.append("  ");
		for(PPGaussian pg : ppGaussians) {
			molVolString.append(pg.encode().trim());
			molVolString.append("  ");
		}
		
		molVolString.append(Integer.toString(volumeGaussians.size()));
		molVolString.append("  ");
		
		for(VolumeGaussian vg : volumeGaussians) {
			molVolString.append(vg.encode());
			molVolString.append("  ");
		}

		double[] hydrogenCoords = new double[3*hydrogens.size()];
		for(int i=0;i<hydrogens.size();i++) {
			hydrogenCoords[3*i] = hydrogens.get(i).x;
			hydrogenCoords[3*i+1] = hydrogens.get(i).y;
			hydrogenCoords[3*i+2] = hydrogens.get(i).z;
		}

		molVolString.append(EncoderFloatingPointNumbers.encode(hydrogenCoords,13));

		return molVolString.toString();
	}
	
	public String encodeCoordsOnly() {
		StringBuilder molVolString = new StringBuilder();
		//molVolString.append(Integer.toString(atomicGaussians.size()));
		double[] coords = new double[3*atomicGaussians.size()];
		for(int i=0;i<atomicGaussians.size();i++) {
			coords[3*i]=atomicGaussians.get(i).getCenter().x;
			coords[3*i+1]=atomicGaussians.get(i).getCenter().y;
			coords[3*i+2]=atomicGaussians.get(i).getCenter().z;
		}
		molVolString.append(EncoderFloatingPointNumbers.encode(coords, 13));
		molVolString.append("  ");

		coords = new double[3*ppGaussians.size()];
		for(int i=0;i<ppGaussians.size();i++) {
			coords[3*i]=ppGaussians.get(i).getCenter().x;
			coords[3*i+1]=ppGaussians.get(i).getCenter().y;
			coords[3*i+2]=ppGaussians.get(i).getCenter().z;
		}
		
		molVolString.append(EncoderFloatingPointNumbers.encode(coords, 13));
		molVolString.append("  ");
		
		coords = new double[3*ppGaussians.size()];
		for(int i=0;i<ppGaussians.size();i++) {
			coords[3*i]=ppGaussians.get(i).getPharmacophorePoint().getDirectionality().x;
			coords[3*i+1]=ppGaussians.get(i).getPharmacophorePoint().getDirectionality().y;
			coords[3*i+2]=ppGaussians.get(i).getPharmacophorePoint().getDirectionality().z;
		}
		molVolString.append(EncoderFloatingPointNumbers.encode(coords, 13));
		molVolString.append("  ");
		
		coords = new double[3*volumeGaussians.size()];
		for(int i=0;i<volumeGaussians.size();i++) {
			coords[3*i]=volumeGaussians.get(i).getReferenceVector().x;
			coords[3*i+1]=volumeGaussians.get(i).getReferenceVector().y;
			coords[3*i+2]=volumeGaussians.get(i).getReferenceVector().z;
		}
		molVolString.append(EncoderFloatingPointNumbers.encode(coords, 13));
		molVolString.append("  ");
		
		coords = new double[3*volumeGaussians.size()];
		for(int i=0;i<volumeGaussians.size();i++) {
			coords[3*i]=volumeGaussians.get(i).getShiftVector().x;
			coords[3*i+1]=volumeGaussians.get(i).getShiftVector().y;
			coords[3*i+2]=volumeGaussians.get(i).getShiftVector().z;
		}

		molVolString.append(EncoderFloatingPointNumbers.encode(coords, 13));
		molVolString.append("  ");

		double[] hydrogenCoords = new double[3*hydrogens.size()];
		for(int i=0;i<hydrogens.size();i++) {
			hydrogenCoords[3*i] = hydrogens.get(i).x;
			hydrogenCoords[3*i+1] = hydrogens.get(i).y;
			hydrogenCoords[3*i+2] = hydrogens.get(i).z;
		}

		molVolString.append(EncoderFloatingPointNumbers.encode(hydrogenCoords,13));

		return molVolString.toString();
	}

	public static MolecularVolume decodeCoordsOnly(String string, MolecularVolume reference)  {
		List<AtomicGaussian> referenceAtomicGaussians = reference.getAtomicGaussians(); 
		List<PPGaussian> referencePPGaussians = reference.getPPGaussians(); 
		List<VolumeGaussian> referenceVolGaussians = reference.getVolumeGaussians();

		String[] splitString = string.split("  ");
		double[] atomicGaussiansCoords = EncoderFloatingPointNumbers.decode(splitString[0]);
		double[] ppGaussiansCoords = EncoderFloatingPointNumbers.decode(splitString[1]);
		double[] ppGaussiansDirectionalities = EncoderFloatingPointNumbers.decode(splitString[2]);
		double[] volumeGaussiansRefCoords = EncoderFloatingPointNumbers.decode(splitString[3]);
		double[] volumeGaussiansShiftCoords = EncoderFloatingPointNumbers.decode(splitString[4]);
		double[] hydrogensCoords = EncoderFloatingPointNumbers.decode(splitString[5]);
		
		ArrayList<AtomicGaussian> atomicGaussians = new ArrayList<AtomicGaussian>();
		ArrayList<PPGaussian> ppGaussians = new ArrayList<PPGaussian>();
		ArrayList<VolumeGaussian> volumeGaussians = new ArrayList<VolumeGaussian>();
		ArrayList<Coordinates> hydrogens = new ArrayList<Coordinates>();
		
		int nrOfAtomicGaussians = atomicGaussiansCoords.length/3;
		int nrOfHydrogens = hydrogensCoords.length/3;
		int nrOfPPGaussians = ppGaussiansCoords.length/3;
		int nrOfVolumeGaussians = volumeGaussiansRefCoords.length/3;
		
		for(int i=0;i<nrOfAtomicGaussians;i++) {
			Coordinates coords = new Coordinates(atomicGaussiansCoords[i*3],atomicGaussiansCoords[i*3+1],atomicGaussiansCoords[i*3+2]);
			AtomicGaussian at = new AtomicGaussian(referenceAtomicGaussians.get(i));
			at.setCenter(coords);
			atomicGaussians.add(at);
		}

		for(int i=0;i<nrOfPPGaussians;i++) {
			Coordinates coords = new Coordinates(ppGaussiansCoords[i*3],ppGaussiansCoords[i*3+1],ppGaussiansCoords[i*3+2]);
			PPGaussian pp = new PPGaussian(referencePPGaussians.get(i));
			pp.setCenter(new Coordinates(coords.x, coords.y, coords.z));
			Coordinates directionality = new Coordinates(ppGaussiansDirectionalities[i*3],ppGaussiansDirectionalities[i*3+1],ppGaussiansDirectionalities[i*3+2]);
			pp.getPharmacophorePoint().setDirectionality(directionality);
			ppGaussians.add(pp);
		}
		
		for(int i=0;i<nrOfVolumeGaussians;i++) {
			Coordinates coords = new Coordinates(volumeGaussiansRefCoords[i*3],volumeGaussiansRefCoords[i*3+1],volumeGaussiansRefCoords[i*3+2]);
			VolumeGaussian vg = new VolumeGaussian(referenceVolGaussians.get(i));
			vg.setReferenceVector(new Coordinates(coords.x, coords.y, coords.z));
			Coordinates shift = new Coordinates(volumeGaussiansShiftCoords[i*3],volumeGaussiansShiftCoords[i*3+1],volumeGaussiansShiftCoords[i*3+2]);
			vg.setShiftVector(shift);
			volumeGaussians.add(vg);
		}

		for(int i=0;i<nrOfHydrogens;i++)
			hydrogens.add(new Coordinates(hydrogensCoords[i*3],hydrogensCoords[i*3+1],hydrogensCoords[i*3+2]));

		return new MolecularVolume(atomicGaussians,ppGaussians, volumeGaussians,hydrogens);
	}

	public static MolecularVolume decodeFull(String string, StereoMolecule refMol)  {
		String[] splitString = string.split("  ");
		int nrOfAtomicGaussians = Integer.decode(splitString[0].trim());
		int firstIndex = 1;
		int lastIndex = 1+nrOfAtomicGaussians;
		List<AtomicGaussian> atomicGaussians = new ArrayList<AtomicGaussian>();
		List<PPGaussian> ppGaussians = new ArrayList<PPGaussian>();
		List<VolumeGaussian> volumeGaussians = new ArrayList<VolumeGaussian>();
		List<Coordinates> hydrogens = new ArrayList<Coordinates>();
		
		for(int i=firstIndex;i<lastIndex;i++)
			atomicGaussians.add(AtomicGaussian.fromString(splitString[i].trim()));

		int nrOfPPGaussians = Integer.decode(splitString[lastIndex]);
		firstIndex = lastIndex+1;
		lastIndex = firstIndex + nrOfPPGaussians;
		for(int i=firstIndex;i<lastIndex;i++)
			ppGaussians.add(PPGaussian.fromString(splitString[i],refMol));

		int nrOfVolumeGaussians = Integer.decode(splitString[lastIndex]);
		firstIndex = lastIndex+1;
		lastIndex = firstIndex + nrOfVolumeGaussians;
		for(int i=firstIndex;i<lastIndex;i++)
			volumeGaussians.add(VolumeGaussian.fromString(splitString[i],refMol));

		double[] coords = EncoderFloatingPointNumbers.decode(splitString[splitString.length-1]);
		int nrOfHydrogens = coords.length/3;
		for(int i=0;i<nrOfHydrogens;i++)
			hydrogens.add(new Coordinates(coords[i*3],coords[i*3+1],coords[i*3+2]));

		return new MolecularVolume(atomicGaussians,ppGaussians,volumeGaussians,hydrogens);
	}
}

