public static void Factorise(int K, int M, int N, float[,] V, float[,] W, float[,] H, int ITERATIONS)
        {
            double[] rmses = new double[NUM_THREADS];
            float[,] WH = new float[M, N];


            // Computational enhancement with sparse matrices
            MultiMap <int, int> M_sparse;
            MultiMap <int, int> N_sparse;

            FindSparseLocations(M, N, V, out M_sparse, out N_sparse);

            // Random initialisation of W and H
            Tools.InitialiseRandomMatrices(K, M, N, W, H);

            // And now we iterate
            int iteration = 0;

            while (true)
            {
                Logging.Info("Iteration {0}", iteration);

                // Calculate the new WH every now and then to check our progress...
                ++iteration;
                if (0 == iteration % 3)
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_WH(K, M, N, V, W, H, WH, NUM_THREADS, thread_id_local, ref num_threads_running, rmses));
                    }
                    Tools.WaitForThreads(ref num_threads_running);

                    // Tally the RMSE
                    double rmse = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        rmse += rmses[thread_id];
                    }
                    rmse = Math.Sqrt(rmse);
                    Logging.Info("Iteration {0} has an RMSE of {1}", iteration, rmse);

                    // Check exit condition
                    if (iteration >= ITERATIONS)
                    {
                        break;
                    }
                }

                // Calculate the new H
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_H(K, M, N, V, W, H, WH, M_sparse, N_sparse, NUM_THREADS, thread_id_local, ref num_threads_running));
                    }
                    Tools.WaitForThreads(ref num_threads_running);
                }

                // Calculate the new W
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_W(K, M, N, V, W, H, WH, M_sparse, N_sparse, NUM_THREADS, thread_id_local, ref num_threads_running));
                    }
                    Tools.WaitForThreads(ref num_threads_running);
                }
            }
        }
        public static void Factorise(int K, int M, int N, float[,] V, float[,] W, float[,] H, int ITERATIONS)
        {
            double[] rmses = new double[NUM_THREADS];
            float[,] WH = new float[M, N];

            // Random initialisation of W and H
            Tools.InitialiseRandomMatrices(K, M, N, W, H);

            // And now we iterate
            int iteration = 0;

            while (true)
            {
                Logging.Info("Iteration {0}", iteration);

                // Calculate the new WH (for efficiency reasons so we dont have to calc twice)
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_WH(K, M, N, V, W, H, WH, NUM_THREADS, thread_id_local, ref num_threads_running, rmses));
                    }
                    Tools.WaitForThreads(ref num_threads_running);

                    // Tally the RMSE
                    double rmse = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        rmse += rmses[thread_id];
                    }
                    rmse = Math.Sqrt(rmse);
                    Logging.Info("Iteration {0} has an RMSE of {1}", iteration, rmse);
                }


                // Check exit condition
                ++iteration;
                if (iteration >= ITERATIONS)
                {
                    break;
                }

                // Calculate the new P
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_H(K, M, N, V, W, H, WH, NUM_THREADS, thread_id_local, ref num_threads_running));
                    }
                    Tools.WaitForThreads(ref num_threads_running);
                }

                // Calculate the new A
                {
                    int num_threads_running = 0;
                    for (int thread_id = 0; thread_id < NUM_THREADS; ++thread_id)
                    {
                        int thread_id_local = thread_id;
                        ThreadTools.StartThread(o => PARALLEL_W(K, M, N, V, W, H, WH, NUM_THREADS, thread_id_local, ref num_threads_running));
                    }
                    Tools.WaitForThreads(ref num_threads_running);
                }
            }
        }