/**
 * 
 * Written by Márton Münz and Philip C Biggin
 * Copyright (c) University of Oxford, United Kingdom
 * Visit http://sbcb.bioch.ox.ac.uk/jgromacs/
 * 
 * This source code file is part of JGromacs v1.0.
 * 
 * JGromacs v1.0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JGromacs v1.0. is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JGromacs v1.0. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package jgromacs.analysis;

import java.util.ArrayList;

import jgromacs.data.IndexSet;
import jgromacs.data.Point3D;
import jgromacs.data.PointList;
import jgromacs.data.Structure;
import jgromacs.data.Trajectory;
import jama.EigenvalueDecomposition;
import jama.Matrix;

/**
 * Collection of methods for analysing molecular motions
 *
 */
public class Dynamics {
	
	// Covariance and correlation matrices
	
	
	/**
	 * Calculates the 3Nx3N coordinate covariance matrix from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param reference reference frame
	 * @return coordinate covariance matrix
	 */
	public static Matrix getCoordinateCovarianceMatrix(Trajectory t, PointList reference){
		int numofatoms = t.getNumberOfAtoms();
		int numofframes = t.getNumberOfFrames();
		Matrix ret = new Matrix(3*numofatoms,3*numofatoms,0);
		t = Superposition.superposeTo(t, reference);
		PointList mean = t.getMeanFrame();
		for (int i = 0; i < numofframes; i++) {
			PointList frame = t.getFrameAsPointList(i);
			for (int k = 0; k < numofatoms; k++) {
				Point3D pointk = frame.getPoint(k);
				Point3D meank = mean.getPoint(k);
				double dx_k = pointk.getX()-meank.getX();
				double dy_k = pointk.getY()-meank.getY();
				double dz_k = pointk.getZ()-meank.getZ();
				for (int j = k; j < numofatoms; j++) {
					Point3D pointj = frame.getPoint(j);
					Point3D averagej = mean.getPoint(j);
					double dx_j = pointj.getX()-averagej.getX();
					double dy_j = pointj.getY()-averagej.getY();
					double dz_j = pointj.getZ()-averagej.getZ();
					ret.set(k*3, j*3, ret.get(k*3, j*3)+dx_k*dx_j);
					ret.set(k*3, j*3+1, ret.get(k*3, j*3+1)+dx_k*dy_j);
					ret.set(k*3, j*3+2, ret.get(k*3, j*3+2)+dx_k*dz_j);
					ret.set(k*3+1, j*3, ret.get(k*3+1, j*3)+dy_k*dx_j);
					ret.set(k*3+1, j*3+1, ret.get(k*3+1, j*3+1)+dy_k*dy_j);
					ret.set(k*3+1, j*3+2, ret.get(k*3+1, j*3+2)+dy_k*dz_j);
					ret.set(k*3+2, j*3, ret.get(k*3+2, j*3)+dz_k*dx_j);
					ret.set(k*3+2, j*3+1, ret.get(k*3+2, j*3+1)+dz_k*dy_j);
					ret.set(k*3+2, j*3+2, ret.get(k*3+2, j*3+2)+dz_k*dz_j);
				}
			}
		}
		double n = numofframes; // numofframes-1;
		ret = ret.times(1/n);
		for (int i = 0; i < ret.getRowDimension(); i++) {
			for (int j = i; j < ret.getColumnDimension(); j++) {
				ret.set(j, i, ret.get(i,j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the NxN atomic covariance matrix from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param reference reference frame
	 * @return atomic covariance matrix
	 */
	public static Matrix getAtomicCovarianceMatrix(Trajectory t, PointList reference){
		int numofatoms = t.getNumberOfAtoms();
		int numofframes = t.getNumberOfFrames();
		Matrix ret = new Matrix(numofatoms,numofatoms,0);
		t = Superposition.superposeTo(t, reference);
		PointList mean = t.getMeanFrame();
		for (int i = 0; i < numofframes; i++) {
			PointList frame = t.getFrameAsPointList(i);
			for (int k = 0; k < numofatoms; k++) {
				Point3D pointk = frame.getPoint(k);
				Point3D meank = mean.getPoint(k);
				Point3D devK = pointk.minus(meank);
				for (int j = k; j < numofatoms; j++) {
					Point3D pointj = frame.getPoint(j);
					Point3D meanj = mean.getPoint(j);
					Point3D devJ = pointj.minus(meanj);
					double value = devK.innerProduct(devJ);
					ret.set(k, j, ret.get(k, j)+value);
				}
			}
		}
		double n = numofframes;  // numofframes-1
		ret = ret.times(1/n);
		for (int i = 0; i < ret.getRowDimension(); i++) {
			for (int j = i; j < ret.getColumnDimension(); j++) {
				ret.set(j, i, ret.get(i,j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the 3Nx3N coordinate correlation matrix from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param reference reference frame
	 * @return coordinate correlation matrix
	 */
	public static Matrix getCoordinateCorrelationMatrix(Trajectory t, PointList reference){
		Matrix cov = getCoordinateCovarianceMatrix(t, reference);
		Matrix ret = new Matrix(cov.getRowDimension(),cov.getColumnDimension());
		for (int i = 0; i < ret.getRowDimension(); i++) {
			for (int j = i; j < ret.getColumnDimension(); j++) {
				ret.set(i, j, cov.get(i, j)/Math.sqrt(cov.get(i, i)*cov.get(j, j)));
				ret.set(j, i, ret.get(i, j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the NxN atomic correlation matrix from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param reference reference frame
	 * @return atomic correlation matrix
	 */
	public static Matrix getAtomicCorrelationMatrix(Trajectory t, PointList reference){
		Matrix cov = getAtomicCovarianceMatrix(t, reference);
		Matrix ret = new Matrix(cov.getRowDimension(),cov.getColumnDimension());
		for (int i = 0; i < ret.getRowDimension(); i++) {
			for (int j = i; j < ret.getColumnDimension(); j++) {
				ret.set(i, j, cov.get(i, j)/Math.sqrt(cov.get(i, i)*cov.get(j, j)));
				ret.set(j, i, ret.get(i, j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the 3Nx3N coordinate covariance matrix from a trajectory using its first frame as the reference frame for superposition
	 * @param t trajectory
	 * @return coordinate covariance matrix
	 */
	public static Matrix getCoordinateCovarianceMatrix(Trajectory t){
		return getCoordinateCovarianceMatrix(t,t.getFirstFrameAsPointList());
	}
	
	/**
	 * Calculates the NxN atomic covariance matrix from a trajectory using its first frame as the reference frame for superposition
	 * @param t trajectory
	 * @return atomic covariance matrix
	 */
	public static Matrix getAtomicCovarianceMatrix(Trajectory t){
		return getAtomicCovarianceMatrix(t,t.getFirstFrameAsPointList());
	}
	
	/**
	 * Calculates the 3Nx3N coordinate correlation matrix from a trajectory using its first frame as the reference frame for superposition
	 * @param t trajectory
	 * @return coordinate correlation matrix
	 */
	public static Matrix getCoordinateCorrelationMatrix(Trajectory t){
		return getCoordinateCorrelationMatrix(t,t.getFirstFrameAsPointList());
	}
	
	/**
	 * Calculates the NxN atomic correlation matrix from a trajectory using its first frame as the reference frame for superposition
	 * @param t trajectory
	 * @return atomic correlation matrix
	 */
	public static Matrix getAtomicCorrelationMatrix(Trajectory t){
		return getAtomicCorrelationMatrix(t,t.getFirstFrameAsPointList());
	}
	
	// PCA and related
	
	/**
	 * Calculates the principal components and the corresponding eigenvalues from a covariance matrix 
	 * @param covariance covariance matrix
	 * @return a Matrix array containing D (block diagonal eigenvalue matrix) and V (principal component matrix) 
	 */
	public static Matrix[] getPCA(Matrix covariance){
		EigenvalueDecomposition evd = new EigenvalueDecomposition(covariance);
		Matrix D = evd.getD();
		Matrix V = evd.getV();
		Matrix[] ret = new Matrix[2];
		ret[0] = D;
		ret[1] = V;
		return ret;
	}
	
	/**
	 * Calculates the principal components and the corresponding eigenvalues from a trajectory
	 * @param t trajectory
	 * @return a Matrix array containing D (block diagonal eigenvalue matrix) and V (principal component matrix) 
	 */
	public static Matrix[] getPCA(Trajectory t){
		Matrix covariance = getCoordinateCovarianceMatrix(t);
		return getPCA(covariance);
	}
	
	/**
	 * Calculates the cumulative variance profile from a trajectory
	 * @param t trajectory
	 * @return cumulative variance profile
	 */
	public static ArrayList<Double> getCumulativeVariances(Trajectory t){
		ArrayList<Double> ret = new ArrayList<Double>();
		Matrix[] result = Dynamics.getPCA(t);
		Matrix D = result[0];
		int dim = D.getRowDimension();
		double N = D.trace();
		double sum = 0;
		for (int i = 0; i < dim; i++) {
			sum+=D.get(dim-i-1, dim-i-1);
			ret.add(sum/N);
		}
		return ret;
	}
	
	/**
	 * Calculates the cumulative variance profile from a covariance matrix
	 * @param covariance covariance matrix
	 * @return cumulative variance profile
	 */
	public static ArrayList<Double> getCumulativeVariances(Matrix covariance){
		ArrayList<Double> ret = new ArrayList<Double>();
		Matrix[] result = Dynamics.getPCA(covariance);
		Matrix D = result[0];
		int dim = D.getRowDimension();
		double N = D.trace();
		double sum = 0;
		for (int i = 0; i < dim; i++) {
			sum+=D.get(dim-i-1, dim-i-1);
			ret.add(sum/N);
		}
		return ret;
	}
	
	/**
	 * Calculates the root mean square inner product (RMSIP) from two covariance matrices
	 * @param covariance1 first covariance matrix
	 * @param covariance2 second covariance matrix
	 * @param N number of principal components used from the first trajectory 
	 * @param M number of principal components used from the second trajectory 
	 * @return root mean square inner product (RMSIP) 
	 */
	public static double getRootMeanSquareInnerProduct(Matrix covariance1, Matrix covariance2, int N, int M){
		EigenvalueDecomposition evd1 = new EigenvalueDecomposition(covariance1);
		Matrix V1 = evd1.getV();
		EigenvalueDecomposition evd2 = new EigenvalueDecomposition(covariance2);
		Matrix V2 = evd2.getV();
		double ret = 0;
		int dim = evd1.getD().getColumnDimension();
		for (int i = dim-1; i >= dim-N; i--) {
			for (int j = dim-1; j >= dim-M; j--)
			ret+=product(V1,V2,i,j);
		}
		return Math.sqrt(ret/N);
	}
	
	private static double product(Matrix V1, Matrix V2, int i, int j){
		double ret = 0;
		for (int k = 0; k < V1.getRowDimension(); k++)
			ret+=V1.get(k, i)*V2.get(k, j);
		return ret*ret;
	}
	
	/**
	 * Calculates the root mean square inner product (RMSIP) from two trajectories
	 * @param t1 first trajectory
	 * @param t2 second trajectory
	 * @param N number of principal components used from the first trajectory 
	 * @param M number of principal components used from the second trajectory 
	 * @return root mean square inner product (RMSIP) 
	 */
	public static double getRootMeanSquareInnerProduct(Trajectory t1, Trajectory t2, int N, int M){
		Matrix covariance1 = getCoordinateCovarianceMatrix(t1);
		Matrix covariance2 = getCoordinateCovarianceMatrix(t2);
		return getRootMeanSquareInnerProduct(covariance1,covariance2,N,M);
	}
	
	/**
	 * Calculates the covariance overlap of two covariance matrices
	 * @param covariance1 first covariance matrix
	 * @param covariance2 second covariance matrix
	 * @param N number of dimensions used in the calculation
	 * @return covariance overlap
	 */
	public static double getCovarianceMatrixOverlap(Matrix covariance1, Matrix covariance2, int N){
		
		
		EigenvalueDecomposition evd1 = new EigenvalueDecomposition(covariance1);
		Matrix D1 = evd1.getD();
		Matrix V1 = evd1.getV();
		EigenvalueDecomposition evd2 = new EigenvalueDecomposition(covariance2);
		Matrix D2 = evd2.getD();
		Matrix V2 = evd2.getV();
		double A = 0;
		double B = 0;
		int dim = D1.getRowDimension();
			
		for (int i = dim-N; i < dim; i++) A+=(D1.get(i, i)+D2.get(i, i));
		for (int i = dim-N; i < dim; i++) {
			for (int j = dim-N; j < dim; j++) {
				double x = 0;
				for (int k = 0; k < dim; k++) x+=V1.get(k, i)*V2.get(k, j);
				B+= Math.sqrt(Math.abs(D1.get(i, i)*D2.get(j, j)))*Math.pow(x, 2);
			}
		}
		
		//return 1-Math.sqrt(Math.abs((A-2*B)/A));
		return 1-Math.sqrt(A-2*B)/Math.sqrt(A);
		
	}
	
	/**
	 * Calculates the covariance overlap of two covariance matrices
	 * @param covariance1 first covariance matrix
	 * @param covariance2 second covariance matrix
	 * @return covariance overlap
	 */
	public static double getCovarianceMatrixOverlap(Matrix covariance1, Matrix covariance2){
			
		EigenvalueDecomposition evd1 = new EigenvalueDecomposition(covariance1);
		Matrix D1 = evd1.getD();
		Matrix V1 = evd1.getV();
		EigenvalueDecomposition evd2 = new EigenvalueDecomposition(covariance2);
		Matrix D2 = evd2.getD();
		Matrix V2 = evd2.getV();
		double A = 0;
		double B = 0;
		int dim = D1.getRowDimension();
			
		for (int i = 0; i < dim; i++) A+=(D1.get(i, i)+D2.get(i, i));
		for (int i = 0; i < dim; i++) {
			for (int j = 0; j < dim; j++) {
				double x = 0;
				for (int k = 0; k < dim; k++) x+=V1.get(k, i)*V2.get(k, j);
				B+= Math.sqrt(Math.abs(D1.get(i, i)*D2.get(j, j)))*Math.pow(x, 2);
			}
		}
		
		//return 1-Math.sqrt(Math.abs((A-2*B)/A));
		return 1-Math.sqrt(A-2*B)/Math.sqrt(A);
		
	}
	
	/**
	 * Calculates the covariance overlap from two trajectories
	 * @param t1 first trajectory
	 * @param t2 second trajectory
	 * @param N number of dimensions used in the calculation
	 * @return covariance overlap
	 */
	public static double getCovarianceMatrixOverlap(Trajectory t1, Trajectory t2, int N){
		Matrix covariance1 = getCoordinateCovarianceMatrix(t1);
		Matrix covariance2 = getCoordinateCovarianceMatrix(t2);
		return getCovarianceMatrixOverlap(covariance1, covariance2, N);
	}
	
	/**
	 * Calculates the covariance overlap from two trajectories
	 * @param t1 first trajectory
	 * @param t2 second trajectory
	 * @return covariance overlap
	 */
	public static double getCovarianceMatrixOverlap(Trajectory t1, Trajectory t2){
		Matrix covariance1 = getCoordinateCovarianceMatrix(t1);
		Matrix covariance2 = getCoordinateCovarianceMatrix(t2);
		return getCovarianceMatrixOverlap(covariance1, covariance2);
	}
		
	// Fluctuations
	/**
	 * Calculates the F fluctuation matrix (variances of distances) from a trajectory
	 * @param t trajectory
	 * @return F fluctuation matrix
	 */
	public static Matrix getFluctuationMatrix(Trajectory t){
		int dim = t.getNumberOfAtoms();
		Matrix ret = new Matrix(dim, dim, 0);
		Matrix mean = Distances.getMeanAtomicDistanceMatrix(t);
		for (int i = 0; i < t.getNumberOfFrames(); i++) {
			PointList frame = t.getFrameAsPointList(i);
			Matrix D = Distances.getDistanceMatrix(frame);
			for (int j = 0; j < dim; j++) {
				for (int k = j+1; k < dim; k++) {
					ret.set(j, k, ret.get(j, k)+Math.pow(D.get(j, k)-mean.get(j, k),2));
				}
			}
		}
		ret = ret.times(1.0/t.getNumberOfFrames());
		
		for (int i = 0; i < dim; i++) {
			for (int j = i+1; j < dim; j++) {
				ret.set(j, i, ret.get(i, j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the F fluctuation matrix (variances of distances) for a group of atoms
	 * @param t trajectory
	 * @param indices index set of atoms
	 * @return F fluctuation matrix
	 */
	public static Matrix getFluctuationMatrix(Trajectory t, IndexSet indices){
		int dim = indices.getNumberOfIndices();
		Matrix ret = new Matrix(dim, dim, 0);
		Matrix mean = Distances.getMeanAtomicDistanceMatrix(t, indices);
		for (int i = 0; i < t.getNumberOfFrames(); i++) {
			Structure frame = t.getFrameAsStructure(i);
			Matrix D = Distances.getAtomicDistanceMatrix(frame, indices);
			for (int j = 0; j < dim; j++) {
				for (int k = j+1; k < dim; k++) {
					ret.set(j, k, ret.get(j, k)+Math.pow(D.get(j, k)-mean.get(j, k),2));
				}
			}
		}
		ret = ret.times(1.0/t.getNumberOfFrames());
		for (int i = 0; i < dim; i++) {
			for (int j = i+1; j < dim; j++) {
				ret.set(j, i, ret.get(i, j));
			}
		}
		return ret;
	}
	
	/**
	 * Calculates the fluctuation between two subsets of atoms defined as the mean of entries
	 * of the selected submatrix of matrix F (fluctuation matrix)
	 * @param t trajectory
	 * @param set1 first subset
	 * @param set2 second subset
	 * @return fluctuation between the two atom sets
	 */
	public static double getFluctuationOfSubsets(Trajectory t, IndexSet set1, IndexSet set2){
		double ret = 0;
		Matrix F = getFluctuationMatrix(t);
		ArrayList<Integer> indexList1 = t.getFrameAsStructure(0).convertIndicesToArrayListIndices(set1);
		ArrayList<Integer> indexList2 = t.getFrameAsStructure(0).convertIndicesToArrayListIndices(set2);
		for (int i = 0; i < indexList1.size(); i++) {
			for (int j = 0; j < indexList2.size(); j++) {
				ret+=F.get(indexList1.get(i), indexList2.get(j));
			}
		}
		return ret/(indexList1.size()*indexList2.size());
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param R reference frame
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFprofile(Trajectory t, PointList R){
		ArrayList<Double> ret = new ArrayList<Double>();
		int numofatoms = t.getNumberOfAtoms();
		for (int i = 0; i < numofatoms; i++) ret.add(0.0);
		int K = t.getNumberOfFrames();
		for (int k = 0; k < K; k++) {
			PointList frame = t.getFrameAsPointList(k);
			ArrayList<Double> RMSDi = Similarity.getRMSDiProfile(frame, R);
			for (int i = 0; i < numofatoms; i++)
			ret.set(i, ret.get(i)+Math.pow(RMSDi.get(i),2));	
		}
		for (int i = 0; i < numofatoms; i++) ret.set(i, Math.sqrt(ret.get(i)/K));	
		return ret;
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param R reference frame
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFprofile(Trajectory t, Structure R){
		return getRMSFprofile(t,R.getAllAtomCoordinates());
	}
	
	/**
	 * Calculates the RMSF profile of a group of atoms from a trajectory and a reference frame for superposition
	 * @param t trajectory
	 * @param indicesT index set of atoms used from the trajectory
	 * @param R reference frame
	 * @param indicesR index set of atoms used from the reference frame
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFprofile(Trajectory t, IndexSet indicesT, Structure R, IndexSet indicesR){
		ArrayList<Double> ret = new ArrayList<Double>();
		ArrayList<Integer> indexListT = t.getFrameAsStructure(0).convertIndicesToArrayListIndices(indicesT);
		ArrayList<Integer> indexListR = R.convertIndicesToArrayListIndices(indicesR);
		PointList reference = R.getAllAtomCoordinates().getSubList(indexListR);
		int numofatoms = indexListT.size();
		for (int i = 0; i < numofatoms; i++) ret.add(0.0);
		int K = t.getNumberOfFrames();
		for (int k = 0; k < K; k++) {
			PointList frame = t.getFrameAsPointList(k).getSubList(indexListT);
			ArrayList<Double> RMSDi = Similarity.getRMSDiProfile(frame, reference);
			for (int i = 0; i < numofatoms; i++)
			ret.set(i, ret.get(i)+Math.pow(RMSDi.get(i),2));	
		}
		for (int i = 0; i < numofatoms; i++) ret.set(i, Math.sqrt(ret.get(i)/K));	
		return ret;
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory by superposing all frames to a reference frame R
	 * and calculating the RMSDi deviations with regards to a reference frame Q
	 * @param t trajectory
	 * @param R reference frame to which all frames are superposed
	 * @param Q reference frame from which deviations are measured
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFprofile(Trajectory t, PointList R, PointList Q){
		ArrayList<Double> ret = new ArrayList<Double>();
		int numofatoms = t.getNumberOfAtoms();
		for (int i = 0; i < numofatoms; i++) ret.add(0.0);
		int K = t.getNumberOfFrames();
		for (int k = 0; k < K; k++) {
			PointList frame = t.getFrameAsPointList(k);
			PointList superposed = Superposition.superposeTo(frame, R);
			ArrayList<Double> RMSDi = Similarity.getRMSDiProfileNoSuperposition(superposed, Q);
			for (int i = 0; i < numofatoms; i++)
			ret.set(i, ret.get(i)+Math.pow(RMSDi.get(i),2));	
		}
		for (int i = 0; i < numofatoms; i++) ret.set(i, Math.sqrt(ret.get(i)/K));	
		return ret;
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory by superposing all frames to a reference frame R
	 * and calculating the RMSDi deviations with regards to a reference frame Q
	 * @param t trajectory
	 * @param R reference frame to which all frames are superposed
	 * @param Q reference frame from which deviations are measured
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFprofile(Trajectory t, Structure R, Structure Q){
		return getRMSFprofile(t,R.getAllAtomCoordinates(),Q.getAllAtomCoordinates());
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory by superposing all frames to a reference frame R
	 * and calculating the RMSDi deviations with regards to the mean structure
	 * @param t trajectory
	 * @param R reference frame
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFaroundTheMeanProfile(Trajectory t, PointList R){
		ArrayList<Double> ret = new ArrayList<Double>();
		int numofatoms = t.getNumberOfAtoms();
		for (int i = 0; i < numofatoms; i++) ret.add(0.0);
		Trajectory superposed = Superposition.superposeTo(t, R);
		PointList mean = superposed.getMeanFrame();
		int K = superposed.getNumberOfFrames();
		for (int k = 0; k < K; k++) {
			PointList frame = superposed.getFrameAsPointList(k);
			ArrayList<Double> RMSDi = Similarity.getRMSDiProfileNoSuperposition(frame, mean);
			for (int i = 0; i < numofatoms; i++)
			ret.set(i, ret.get(i)+Math.pow(RMSDi.get(i),2));	
		}
		for (int i = 0; i < numofatoms; i++) ret.set(i, Math.sqrt(ret.get(i)/K));	
		return ret;
	}
	
	/**
	 * Calculates the RMSF profile from a trajectory by superposing all frames to a reference frame R
	 * and calculating the RMSDi deviations with regards to the mean structure
	 * @param t trajectory
	 * @param R reference frame
	 * @return RMSF profile
	 */
	public static ArrayList<Double> getRMSFaroundTheMeanProfile(Trajectory t, Structure R){
		return getRMSFaroundTheMeanProfile(t,R.getAllAtomCoordinates());
	}
	
	
			
	// Dynamical networks (Sethi et al. 2009)
	
	/**
	 * Calculates the dynamical network of a protein according to the definition of Sethi et al. 2009
	 * (Dynamical networks in tRNA:protein complexes, PNAS)
	 * @param t trajectory
	 * @param cutoff distance cutoff for the calculation of contact matrix
	 * @param frequency minimal frequency of frames in which two residues must be in contact
	 * @return weighted adjacency matrix of dynamical network
	 */
	public static Matrix getDynamicalNetwork(Trajectory t, double cutoff, double frequency){
		   int d = t.getNumberOfResidues();
	       Matrix Adjacency = new Matrix(d,d,0);
	       Matrix Contact = Distances.getFrequencyContactMatrix(t, Distances.CLOSESTHEAVY, cutoff, frequency);  
	       IndexSet alphaCarbons = t.getFirstFrameAsStructure().getAlphaCarbonIndexSet();
	       t = t.getSubTrajectory(alphaCarbons);
	       Matrix Correlation = Dynamics.getAtomicCorrelationMatrix(t);
	       for (int i = 0; i < d; i++) {
	            for (int j = i+1; j < d; j++) {
	            	//if (Contact.get(i, j)==1) Adjacency.set(i, j, Math.abs(Correlation.get(i, j)));
	            	if (Contact.get(i, j)==1) Adjacency.set(i, j, -Math.log(Math.abs(Correlation.get(i, j))));  
	            	else Adjacency.set(i, j, Double.NaN);
	                Adjacency.set(j, i, Adjacency.get(i, j));
	           }
	       }    
		return Adjacency;
	}
	
	// Structural radius (Kuzmanic and Zagrovic, 2010)

	/**
	 * Calculates the structural radius of the conformational ensemble sampled in the trajectory
	 * as defined by Kuzmanic and Zagrovic, 2010
	 * (Determination of Ensemble-Average Pairwise Root Mean-Square Deviation from Experimental B-Factors, Biophysical Journal)
	 * @param t trajectory
	 * @return structural radius
	 */
	public static double getStructuralRadius(Trajectory t){
		double ret = 0;
		int Ns = t.getNumberOfFrames();
		for (int i = 0; i < Ns; i++) {
			PointList frameI = t.getFrameAsPointList(i);
			for (int j = 0; j < Ns; j++) {
				PointList frameJ = t.getFrameAsPointList(j);
				ret+=Math.pow(Similarity.getRMSD(frameI, frameJ), 2);
			}
		}
		return Math.sqrt(ret/(2*Ns*Ns));
	}
	
	// Ensemble averaged RMSD (Brüschweiler, 2002)

	/**
	 * Calculates the ensemble averaged RMSD between two conformational ensembles sampled in two trajectories
	 * as defined by Brüschweiler, 2002
	 * (Efficient RMSD measures for the comparison of two molecular ensembles, Proteins: Structure, Function, and Bioinformatics)
	 * @param t1 first trajectory
	 * @param t2 second trajectory
	 * @return ensemble averaged RMSD
	 */
	public static double getEnsembleAveragedRMSD(Trajectory t1, Trajectory t2){
		double ret = 0;
		int N = t1.getNumberOfFrames();
		int M = t2.getNumberOfFrames();
		for (int i = 0; i < N; i++) {
			PointList frameI = t1.getFrameAsPointList(i);
			for (int j = 0; j < M; j++) {
				PointList frameJ = t2.getFrameAsPointList(j);
				ret+=Math.pow(Similarity.getRMSD(frameI, frameJ), 2);
			}
		}
		double n = Double.valueOf(N*M);
		return Math.sqrt(ret/n);
	}
	
	// Contact probability map (Wei et al. 2009 )

	/**
	 * Calculates the (residue) contact probability map for a trajectory
	 * as defined by Wei et al, 2009
	 * (Residual Structure in Islet Amyloid Polypeptide Mediates Its Interactions with Soluble Insulin, Biochemistry)
	 * @param t simulation trajectory
	 * @param cutoff distance cutoff
	 * @return contact probability matrix
	 */
	public static Matrix getContactProbabilityMap(Trajectory t, double cutoff){
		int dim = t.getNumberOfResidues();
		Matrix ret = new Matrix(dim,dim,0);
		for (int k = 0; k < t.getNumberOfFrames(); k++) {
			Structure frame = t.getFrameAsStructure(k);
			Matrix C = Distances.getContactMatrix(frame, Distances.CLOSESTHEAVY, cutoff);
			ret = ret.plus(C);
		}
		ret = ret.times(1.0/t.getNumberOfFrames());
		return ret;
	}
	
	// Trajectory of a single atom
	
	/**
	 * Returns the trajectory of a single atom in the course of the simulation
	 * @param t simulation trajectory
	 * @param atomindex index of atom
	 * @return atomic trajectory as an ArrayList of coordinates
	 */
	public static ArrayList<Point3D> getTrajectoryOfAtom(Trajectory t, int atomindex){
		ArrayList<Point3D> ret = new ArrayList<Point3D>();
		for (int i = 0; i < t.getNumberOfFrames(); i++) 
		ret.add(t.getFrameAsStructure(i).getAtomByIndex(atomindex).getCoordinates());
		return ret;
	}
	
}
