Class to represent an original dataset, its Procrustes form and all necessary data (i.e. rotation, center, scale...)
        /// <summary>
        /// Applies the rotation operator to the given dataset according to the reference dataset
        /// </summary>
        /// <param name="p">Procrusted dataset to rotate</param>
        /// <param name="p_reference">Reference procrusted dataset</param>
        /// <returns>The rotated dataset</returns>
        double[,] Rotate(ProcrustedDataset p, ProcrustedDataset p_reference)
        {
            // Rotation calculus per Amy Ross, Procrustes Analysis : http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.119.2686&rep=rep1&type=pdf

            SingularValueDecomposition svd = new SingularValueDecomposition(p_reference.Dataset.TransposeAndDot(p.Dataset));

            double[,] Q = svd.RightSingularVectors.TransposeAndDot(svd.LeftSingularVectors);

            p.RotationMatrix = Q;

            return(p.Dataset.Dot(Q));
        }
        /// <summary>
        /// Apply Procrustes translation and scale to the given dataset
        /// </summary>
        ///
        /// <param name="p">Procrusted dataset to process and store the results to</param>
        ///
        /// <param name="samples">The dataset itself</param>
        ///
        private void ApplyTranslateScale(ProcrustedDataset p, double[,] samples)
        {
            // Save the original data
            p.Source = samples;

            // Save the original data center (i.e. mean)
            p.Center = p.Source.Mean(dimension: 0);
            // Translate the samples to zero
            p.Dataset = Translate(samples);

            // Save the original scale of the dataset
            p.Scale = GetDatasetScale(p.Dataset);
            // Scale the dataset to 1
            p.Dataset = Scale(p.Dataset);
        }
        /// <summary>
        /// Transforms the dataset to match the given reference original dataset
        /// </summary>
        /// <param name="p_reference">Dataset to match</param>
        /// <returns>The transformed dataset matched to the reference</returns>
        public double[,] Transform(ProcrustedDataset p_reference)
        {
            // Make a copy of the current Procrustes dataset
            double[,] tData = (double[, ])Dataset.Clone();

            // Rotate the dataset to match the reference dataset rotation
            tData = tData.Dot(p_reference.RotationMatrix.Transpose());

            // Scale the dataset to match the reference scale
            tData = tData.Multiply(p_reference.Scale);

            // Prepare a negative translation vector to...
            double[] refCenter = p_reference.Center.Multiply(-1);
            // ... move the dataset to the same center as the reference
            tData = tData.Center(refCenter);

            return(tData);
        }
        /// <summary>
        ///   Compute the Procrustes analysis to extract Procrustes distances and models by specifying the reference dataset
        /// </summary>
        ///
        /// <param name="reference_sample_index">Index of the reference dataset. If out of bounds of the sample array, the first dataset is used.</param>
        /// <param name="samples">List of sample data sets to analyze</param>
        ///
        /// <returns>Procrustes distances of the analyzed samples</returns>
        ///
        public double[,] Compute(int reference_sample_index, params double[][,] samples)
        {
            // Check arguments in case of mistakes
            CheckSampleDataArgument(samples);

            // If reference index out of bounds...
            if (reference_sample_index < 0 || reference_sample_index >= samples.Length)
            {
                // Use the first element of the array
                reference_sample_index = 0;
            }

            // Allocate space for the computed results
            ProcrustedDatasets = new ProcrustedDataset[samples.Length];

            // Start with the reference dataset
            ProcrustedDatasets[reference_sample_index] = new ProcrustedDataset();
            // The rotation martrix of the reference is the identity matrix (i.e. no rotation since it's the reference itself)
            ProcrustedDatasets[reference_sample_index].RotationMatrix = Matrix.Identity(samples[reference_sample_index].GetLength(1));
            // Translate then scale the dataset to set it with scale = 1 and center position at zero
            ApplyTranslateScale(ProcrustedDatasets[reference_sample_index], samples[reference_sample_index]);

            // For each data set
            for (int i = 0; i < samples.Length; i++)
            {
                // Except for the reference
                if (i != reference_sample_index)
                {
                    ProcrustedDatasets[i] = new ProcrustedDataset();
                    // Translate then scale the dataset
                    ApplyTranslateScale(ProcrustedDatasets[i], samples[i]);

                    // Finally, rotate the sample to fit the reference data rotation
                    ProcrustedDatasets[i].Dataset = Rotate(ProcrustedDatasets[i], ProcrustedDatasets[reference_sample_index]);
                }
            }

            // Update the Procrustes distance matrix
            UpdateProcrustesDistances(ProcrustedDatasets);

            return(ProcrustesDistances);
        }
        /// <summary>
        /// Transforms the dataset to match the given reference original dataset
        /// </summary>
        /// <param name="p_reference">Dataset to match</param>
        /// <returns>The transformed dataset matched to the reference</returns>
        public double[,] Transform(ProcrustedDataset p_reference)
        {
            // Make a copy of the current Procrustes dataset
            double[,] tData = (double[,])Dataset.Clone();

            // Rotate the dataset to match the reference dataset rotation
            tData = tData.Dot(p_reference.RotationMatrix.Transpose());

            // Scale the dataset to match the reference scale
            tData = tData.Multiply(p_reference.Scale);

            // Prepare a negative translation vector to...
            double[] refCenter = p_reference.Center.Multiply(-1);
            // ... move the dataset to the same center as the reference
            tData = tData.Center(refCenter);

            return tData;
        }
        /// <summary>
        ///   Compute the Procrustes analysis to extract Procrustes distances and models by specifying the reference dataset
        /// </summary>
        /// 
        /// <param name="reference_sample_index">Index of the reference dataset. If out of bounds of the sample array, the first dataset is used.</param>
        /// <param name="samples">List of sample data sets to analyze</param>
        /// 
        /// <returns>Procrustes distances of the analyzed samples</returns>
        /// 
        public double[,] Compute(int reference_sample_index, params double[][,] samples)
        {
            // Check arguments in case of mistakes
            CheckSampleDataArgument(samples);

            // If reference index out of bounds...
            if (reference_sample_index < 0 || reference_sample_index >= samples.Length)
            {
                // Use the first element of the array
                reference_sample_index = 0;
            }

            // Allocate space for the computed results
            ProcrustedDatasets = new ProcrustedDataset[samples.Length];

            // Start with the reference dataset
            ProcrustedDatasets[reference_sample_index] = new ProcrustedDataset();
            // The rotation martrix of the reference is the identity matrix (i.e. no rotation since it's the reference itself)
            ProcrustedDatasets[reference_sample_index].RotationMatrix = Matrix.Identity(samples[reference_sample_index].GetLength(1));
            // Translate then scale the dataset to set it with scale = 1 and center position at zero
            ApplyTranslateScale(ProcrustedDatasets[reference_sample_index], samples[reference_sample_index]);

            // For each data set
            for (int i = 0; i < samples.Length; i++)
            {
                // Except for the reference
                if (i != reference_sample_index)
                {
                    ProcrustedDatasets[i] = new ProcrustedDataset();
                    // Translate then scale the dataset
                    ApplyTranslateScale(ProcrustedDatasets[i], samples[i]);

                    // Finally, rotate the sample to fit the reference data rotation
                    ProcrustedDatasets[i].Dataset = Rotate(ProcrustedDatasets[i], ProcrustedDatasets[reference_sample_index]);
                }
            }

            // Update the Procrustes distance matrix
            UpdateProcrustesDistances(ProcrustedDatasets);

            return ProcrustesDistances;
        }
        /// <summary>
        /// Apply Procrustes translation and scale to the given dataset
        /// </summary>
        /// 
        /// <param name="p">Procrusted dataset to process and store the results to</param>
        /// 
        /// <param name="samples">The dataset itself</param>
        /// 
        private void ApplyTranslateScale(ProcrustedDataset p, double[,] samples)
        {
            // Save the original data
            p.Source = samples;

            // Save the original data center (i.e. mean)
            p.Center = p.Source.Mean(dimension: 0);
            // Translate the samples to zero
            p.Dataset = Translate(samples);

            // Save the original scale of the dataset
            p.Scale = GetDatasetScale(p.Dataset);
            // Scale the dataset to 1
            p.Dataset = Scale(p.Dataset);
        }
        /// <summary>
        /// Applies the rotation operator to the given dataset according to the reference dataset
        /// </summary>
        /// <param name="p">Procrusted dataset to rotate</param>
        /// <param name="p_reference">Reference procrusted dataset</param>
        /// <returns>The rotated dataset</returns>
        double[,] Rotate(ProcrustedDataset p, ProcrustedDataset p_reference)
        {
            // Rotation calculus per Amy Ross, Procrustes Analysis : http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.119.2686&rep=rep1&type=pdf

            SingularValueDecomposition svd = new SingularValueDecomposition(p_reference.Dataset.TransposeAndDot(p.Dataset));

            double[,] Q = svd.RightSingularVectors.TransposeAndDot(svd.LeftSingularVectors);

            p.RotationMatrix = Q;

            return p.Dataset.Dot(Q);
        }