/***************************************************************************************************
*           C# sample for the usage of TopoART-R (classes TopoART_R and Fast_TopoART_R)            *
*                                  (approximation of a sine function)                              *
****************************************************************************************************
*                           Created by Marko Tscherepanow, 23 November 2016                        *
***************************************************************************************************/

// Compile and run from the console: dotnet run --project TopoART-R_sample2.csproj

using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using LibTopoART;

namespace LibTopoART_samples
{
	/// <summary>
	/// Regression sample using TopoART-R. (advanced version) [C#]
	/// <para>
	/// This sample trains a TopoART-R network with 100 points sampled from a sine function.
	/// Then, sine values are predicted for 25 random values.
	/// </para>
	/// <para>The predicted results can be visualised using the script <c>ShowTopoARTRResults</c> provided 
	/// for R and MATLAB in the subfolder <c>visualisation</c>.
	/// </para>
	/// </summary>
	class TopoART_R_sample2
	{
		enum Selector {
			USE_TopoART_R,
			USE_Fast_TopoART_R,
		};

		private static void Main()
		{
			// Destination directory for the regression results
			const string resultPath = "../../../../../results/regression/";

			// Input and output dimensions
			const long iLen = 2;	// number of independent variables (input dimensions)
			const long dLen = 1;	// number of dependent variables (output dimensions)

			// Choose TopoART-R implementation (see enum Selector above)
			var sel = Selector.USE_Fast_TopoART_R;

			// Training data
			const long trainingSampleNum	= 100; 
			const long testSampleNum		= 25; 
			var iValues = new decimal[trainingSampleNum][];
			var dValues = new decimal[trainingSampleNum][];

			var rnd = new Random();

			// Set working directory to assembly directory
			Directory.SetCurrentDirectory(Path.GetDirectoryName(new Uri(Assembly.GetEntryAssembly().Location).LocalPath));

			// Create training samples (sine function)
			for(long i = 0; i < trainingSampleNum; ++i) {

				// Set the independent variables
				iValues[i]		=	new decimal[iLen];
				iValues[i][0]	=	i * (decimal)Math.PI * 2.0m / (trainingSampleNum - 1);

				// Simulate a second independent variable
				iValues[i][1]	=	(decimal)rnd.NextDouble();

				// Set the dependent variable
				dValues[i]		=	new decimal[dLen];
				dValues[i][0]	=	((decimal)Math.Sin((double)iValues[i][0]) + 1.0m) / 2.0m; 

				// Normalisation of the first independent variable
				iValues[i][0]	/=	((decimal)Math.PI * 2.0m);
			}

			// Create TopoART-R network using rho_a = 0.99 and default values for the remaining parameters
			ITopoART_R tar;
			if(sel == Selector.USE_TopoART_R)
				tar = new TopoART_R(iLen, dLen, 2, 0.99m);
			else
				tar = new Fast_TopoART_R(iLen, dLen, 2, 0.99m);

			// Disable topology learning
			tar.SkipEdgeLearning = true;

			// Create file for the results and write the training data
			// The second independent variable is not saved in oder to be compatible with the results of TopoART-R_sample1.
			using(TextWriter writer = new StreamWriter(File.Open(resultPath + "TopoART-R_sine_regression_results.csv", 
				FileMode.Create)))
			{
				writer.WriteLine("   {0,6:D},   {1,6:D}", trainingSampleNum, testSampleNum);

				// Training with separated arrays for the independent variables and the dependent variables
				Console.WriteLine("Train TopoART-R network with {0} samples", trainingSampleNum);

				// Train the network until it stabilises and output the number of required iterations
				long iterationNum = 0;
				do {
					tar.ResetAdaptationState();

					for(long i = 0; i < trainingSampleNum; ++i) {
						tar.Learn(iValues[i], dValues[i]);

						// Write denormalised training data (only in the first iteration)
						if(iterationNum == 0)
							writer.WriteLine("   {0},   {1}", 
								(iValues[i][0] * (decimal)Math.PI * 2.0m).ToString(" 0.000;-0.000", CultureInfo.InvariantCulture), 
								((dValues[i][0] - 0.5m) * 2.0m).ToString(" 0.000;-0.000", CultureInfo.InvariantCulture));
					}

					++iterationNum;
				} while((tar.GetAdaptationState() & AdaptationState.ANY_PERMANENT_ADAPTATION_MASK) != AdaptationState.NO_ADAPTATION);
				Console.WriteLine("Stopped training after {0} iterations", iterationNum);

				// Prediction with a mask vector (only one independent variable is present) 
				var iVecRandom = new decimal[iLen];
				var m_i_vec = new bool[iLen];
				m_i_vec[0] = false;	// first independent variable is presented
				m_i_vec[1] = true;	// second independent variable is missing
				Console.WriteLine("Predict sine function for 25 random values");
				for(long i = 0; i < testSampleNum; ++i) {
					iVecRandom[0] = (decimal)rnd.NextDouble();
					var prediction = tar.Predict(iVecRandom, m_i_vec);
					writer.WriteLine("   {0},   {1}",
						(iVecRandom[0] * (decimal)Math.PI * 2.0m).ToString(" 0.000;-0.000", CultureInfo.InvariantCulture), 
						((prediction.d_vec_prediction[0] - 0.5m) * 2.0m).ToString(" 0.000;-0.000", CultureInfo.InvariantCulture)); 
				}
			}
		}
	}
}