/// <summary>
        /// Tests if the data in <paramref name="data"/> can be used for the DecomposeByColumnContent action.
        /// </summary>
        /// <param name="data">The data to test.</param>
        /// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param>
        public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent)
        {
            if (null == data.DataTable) // this is mandatory, thus an exception is always thrown
            {
                throw new ArgumentNullException("SourceTable is null, it must be set before");
            }

            data.EnsureExistenceOfIdentifier(ColumnX, 1);
            data.EnsureExistenceOfIdentifier(ColumnY, 1);
            data.EnsureExistenceOfIdentifier(ColumnV, 1);

            if (data.GetDataColumns(ColumnV).Count == 0)
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException(!data.ContainsIdentifier(ColumnV) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty");
                }
            }

            if (data.GetDataColumnOrNull(ColumnX) == null)
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException("X column  not included in columnsToProcess");
                }
                else
                {
                    var col = data.GetDataColumns(ColumnV).FirstOrDefault();
                    if (null != col)
                    {
                        data.SetDataColumn(ColumnX, col);
                    }
                }
            }

            if (data.GetDataColumnOrNull(ColumnY) == null)
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException("Y column  not included in columnsToProcess");
                }
                else
                {
                    var col = data.GetDataColumns(ColumnV).FirstOrDefault();
                    if (null != col)
                    {
                        data.SetDataColumn(ColumnY, col);
                    }
                }
            }
        }
        /// <summary>
        /// Tests if the data in <paramref name="data"/> can be used for the ExpandCyclingVariable action.
        /// </summary>
        /// <param name="data">The data to test.</param>
        /// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param>
        public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent)
        {
            if (null == data.DataTable) // this is mandatory, thus an exception is always thrown
            {
                throw new ArgumentNullException("SourceTable is null, it must be set before");
            }

            data.EnsureExistenceOfIdentifier(ColumnsParticipatingIdentifier);
            data.EnsureExistenceOfIdentifier(ColumnWithCyclingVariableIdentifier, 1);
            data.EnsureExistenceOfIdentifier(ColumnsToAverageIdentifier);

            if (data.GetDataColumns(ColumnsParticipatingIdentifier).Count == 0)
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException(!data.ContainsIdentifier(ColumnsParticipatingIdentifier) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty");
                }
            }

            if (data.GetDataColumnOrNull(ColumnWithCyclingVariableIdentifier) == null)
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException("Column with cycling variable was not included in columnsToProcess");
                }
                else
                {
                    var col = data.GetDataColumns(ColumnsParticipatingIdentifier).FirstOrDefault();
                    if (null != col)
                    {
                        data.SetDataColumn(ColumnWithCyclingVariableIdentifier, col);
                    }
                }
            }

            if (!data.ContainsIdentifier(ColumnsToAverageIdentifier))
            {
                if (throwIfNonCoherent)
                {
                    throw new ArgumentException("ColumnsToAverage collection is not included");
                }
            }
        }
		/// <summary>
		/// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable.
		/// The provided destination table is cleared from all data and property values before.
		/// </summary>
		/// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param>
		/// <param name="options">The settings for decomposing.</param>
		/// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param>
		/// <returns>Null if the method finishes successfully, or an error information.</returns>
		public static string DecomposeByColumnContent(DataTableMultipleColumnProxy inputData, DecomposeByColumnContentOptions options, DataTable destTable)
		{
			var srcTable = inputData.DataTable;

			try
			{
				DecomposeByColumnContentDataAndOptions.EnsureCoherence(inputData, true);
			}
			catch (Exception ex)
			{
				return ex.Message;
			}

			destTable.DataColumns.RemoveColumnsAll();
			destTable.PropCols.RemoveColumnsAll();

			DataColumn srcCycCol = inputData.GetDataColumnOrNull(DecomposeByColumnContentDataAndOptions.ColumnWithCyclingVariableIdentifier);
			var decomposedValues = Decompose(srcCycCol);
			// the decomposedValues are not sorted yes

			if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Ascending)
			{
				decomposedValues.Sort();
			}
			else if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Descending)
			{
				decomposedValues.Sort();
				decomposedValues.Reverse();
			}
			// get the other columns to process

			var srcColumnsToProcess = new List<DataColumn>(inputData.GetDataColumns(DecomposeByColumnContentDataAndOptions.ColumnsParticipatingIdentifier));
			// subtract the column containing the decompose values
			srcColumnsToProcess.Remove(srcCycCol);

			// the only property column that is now usefull is that with the repeated values
			var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol));

			if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupOneColumn)
			{
				// columns originating from the same column but with different property are grouped together, but they will get different group numbers
				foreach (var srcCol in srcColumnsToProcess)
				{
					int nCreatedCol = -1;
					int nCreatedProp = 0;
					foreach (var prop in decomposedValues)
					{
						++nCreatedCol;
						++nCreatedProp;
						var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp);
						var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
						for (int i = 0, j = 0; i < srcCycCol.Count; ++i)
						{
							if (prop == srcCycCol[i])
							{
								destCol[j] = srcCol[i];
								++j;
							}
						}
						// fill also property column
						destPropCol[nDestCol] = prop;
					}
				}
			}
			else if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupAllColumns)
			{
				// all columns with the same property are grouped together, and those columns will share the same group number
				int nCreatedCol = -1; // running number of processed range for column creation (Naming)
				int nCreatedProp = -1;
				foreach (var prop in decomposedValues)
				{
					++nCreatedProp;
					++nCreatedCol;

					foreach (var srcCol in srcColumnsToProcess)
					{
						var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp);
						var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
						for (int i = 0, j = 0; i < srcCycCol.Count; ++i)
						{
							if (prop == srcCycCol[i])
							{
								destCol[j] = srcCol[i];
								++j;
							}
						}
						// fill also property column
						destPropCol[nDestCol] = prop;
					}
				}
			}
			else
			{
				throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
			}

			return null;
		}
		/// <summary>
		/// Tests if the data in <paramref name="data"/> can be used for the DecomposeByColumnContent action.
		/// </summary>
		/// <param name="data">The data to test.</param>
		/// <param name="throwIfNonCoherent">If true, an exception is thrown if any problems are detected. If false, it is tried to rectify the problem by making some assumtions.</param>
		public static void EnsureCoherence(DataTableMultipleColumnProxy data, bool throwIfNonCoherent)
		{
			if (null == data.DataTable) // this is mandatory, thus an exception is always thrown
			{
				throw new ArgumentNullException("SourceTable is null, it must be set before");
			}

			data.EnsureExistenceOfIdentifier(ColumnsParticipatingIdentifier);
			data.EnsureExistenceOfIdentifier(ColumnWithCyclingVariableIdentifier, 1);

			if (data.GetDataColumns(ColumnsParticipatingIdentifier).Count == 0)
			{
				if (throwIfNonCoherent)
					throw new ArgumentException(!data.ContainsIdentifier(ColumnsParticipatingIdentifier) ? "ColumnsToProcess is not set" : "ColumnsToProcess is empty");
			}

			if (data.GetDataColumnOrNull(ColumnWithCyclingVariableIdentifier) == null)
			{
				if (throwIfNonCoherent)
					throw new ArgumentException("Column with cycling variable was not included in columnsToProcess");
				else
				{
					var col = data.GetDataColumns(ColumnsParticipatingIdentifier).FirstOrDefault();
					if (null != col)
						data.SetDataColumn(ColumnWithCyclingVariableIdentifier, col);
				}
			}
		}
        /// <summary>
        /// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable.
        /// The provided destination table is cleared from all data and property values before.
        /// </summary>
        /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param>
        /// <param name="options">The settings for decomposing.</param>
        /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param>
        /// <returns>Null if the method finishes successfully, or an error information.</returns>
        public static string DecomposeByColumnContent(DataTableMultipleColumnProxy inputData, DecomposeByColumnContentOptions options, DataTable destTable)
        {
            var srcTable = inputData.DataTable;

            try
            {
                DecomposeByColumnContentDataAndOptions.EnsureCoherence(inputData, true);
            }
            catch (Exception ex)
            {
                return(ex.Message);
            }

            destTable.DataColumns.RemoveColumnsAll();
            destTable.PropCols.RemoveColumnsAll();

            DataColumn srcCycCol        = inputData.GetDataColumnOrNull(DecomposeByColumnContentDataAndOptions.ColumnWithCyclingVariableIdentifier);
            var        decomposedValues = Decompose(srcCycCol);

            // the decomposedValues are not sorted yes

            if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Ascending)
            {
                decomposedValues.Sort();
            }
            else if (options.DestinationColumnSorting == DecomposeByColumnContentOptions.OutputSorting.Descending)
            {
                decomposedValues.Sort();
                decomposedValues.Reverse();
            }
            // get the other columns to process

            var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(DecomposeByColumnContentDataAndOptions.ColumnsParticipatingIdentifier));

            // subtract the column containing the decompose values
            srcColumnsToProcess.Remove(srcCycCol);

            // the only property column that is now usefull is that with the repeated values
            var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol));

            if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupOneColumn)
            {
                // columns originating from the same column but with different property are grouped together, but they will get different group numbers
                foreach (var srcCol in srcColumnsToProcess)
                {
                    int nCreatedCol  = -1;
                    int nCreatedProp = 0;
                    foreach (var prop in decomposedValues)
                    {
                        ++nCreatedCol;
                        ++nCreatedProp;
                        var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp);
                        var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                        for (int i = 0, j = 0; i < srcCycCol.Count; ++i)
                        {
                            if (prop == srcCycCol[i])
                            {
                                destCol[j] = srcCol[i];
                                ++j;
                            }
                        }
                        // fill also property column
                        destPropCol[nDestCol] = prop;
                    }
                }
            }
            else if (options.DestinationOutput == DecomposeByColumnContentOptions.OutputFormat.GroupAllColumns)
            {
                // all columns with the same property are grouped together, and those columns will share the same group number
                int nCreatedCol  = -1; // running number of processed range for column creation (Naming)
                int nCreatedProp = -1;
                foreach (var prop in decomposedValues)
                {
                    ++nCreatedProp;
                    ++nCreatedCol;

                    foreach (var srcCol in srcColumnsToProcess)
                    {
                        var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), nCreatedProp);
                        var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                        for (int i = 0, j = 0; i < srcCycCol.Count; ++i)
                        {
                            if (prop == srcCycCol[i])
                            {
                                destCol[j] = srcCol[i];
                                ++j;
                            }
                        }
                        // fill also property column
                        destPropCol[nDestCol] = prop;
                    }
                }
            }
            else
            {
                throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
            }

            return(null);
        }
        /// <summary>
        /// Decompose the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable.
        /// The provided destination table is cleared from all data and property values before.
        /// </summary>
        /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param>
        /// <param name="options">The settings for decomposing.</param>
        /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param>
        /// <returns>Null if the method finishes successfully, or an error information.</returns>
        public static string ConvertXYVToMatrix(DataTableMultipleColumnProxy inputData, ConvertXYVToMatrixOptions options, DataTable destTable)
        {
            var srcTable = inputData.DataTable;

            try
            {
                ConvertXYVToMatrixDataAndOptions.EnsureCoherence(inputData, true);
            }
            catch (Exception ex)
            {
                return(ex.Message);
            }

            destTable.DataColumns.RemoveColumnsAll();
            destTable.PropCols.RemoveColumnsAll();

            DataColumn srcXCol = inputData.GetDataColumnOrNull(ConvertXYVToMatrixDataAndOptions.ColumnX);
            DataColumn srcYCol = inputData.GetDataColumnOrNull(ConvertXYVToMatrixDataAndOptions.ColumnY);

            // X-Values
            IReadOnlyList <AltaxoVariant> clusterValuesX;
            IReadOnlyList <int>           clusterIndicesX;
            IReadOnlyList <double>        clusterStdDevX = null;

            if (options.UseClusteringForX && options.NumberOfClustersX.HasValue && srcXCol is DoubleColumn srcXDbl)
            {
                (clusterValuesX, clusterStdDevX, clusterIndicesX) = ClusterValuesByKMeans(srcXDbl, options.NumberOfClustersX.Value, options.DestinationXColumnSorting, options.CreateStdDevX);
            }
            else
            {
                (clusterValuesX, clusterIndicesX) = ClusterValuesByEquality(srcXCol, options.DestinationXColumnSorting);
            }

            // Y-Values
            IReadOnlyList <AltaxoVariant> clusterValuesY;
            IReadOnlyList <int>           clusterIndicesY;
            IReadOnlyList <double>        clusterStdDevY = null;

            if (options.UseClusteringForY && options.NumberOfClustersY.HasValue && srcYCol is DoubleColumn srcYDbl)
            {
                (clusterValuesY, clusterStdDevY, clusterIndicesY) = ClusterValuesByKMeans(srcYDbl, options.NumberOfClustersY.Value, options.DestinationYColumnSorting, options.CreateStdDevY);
            }
            else
            {
                (clusterValuesY, clusterIndicesY) = ClusterValuesByEquality(srcYCol, options.DestinationYColumnSorting);
            }


            // get the other columns to process
            var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(ConvertXYVToMatrixDataAndOptions.ColumnV));

            // subtract the column containing the decompose values
            srcColumnsToProcess.Remove(srcXCol);
            srcColumnsToProcess.Remove(srcYCol);

            int xOffset = 1 + (clusterStdDevY != null ? 1 : 0);
            // the only property column that is now useful is that with the repeated values
            var destXCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, 0);

            for (int i = 0; i < xOffset; ++i)
            {
                destXCol[0] = double.NaN;
            }
            for (int i = 0; i < clusterValuesX.Count; ++i)
            {
                destXCol[i + xOffset] = clusterValuesX[i]; // leave index 0 and maybe 1for the y-column
            }
            if (clusterStdDevX != null)
            {
                var stdXCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol) + "_StdDev", srcXCol.GetType(), ColumnKind.Err, 0);
                for (int i = 0; i < xOffset; ++i)
                {
                    stdXCol[0] = double.NaN;
                }
                for (int i = 0; i < clusterStdDevX.Count; ++i)
                {
                    stdXCol[i + xOffset] = clusterStdDevX[i]; // leave index 0 and maybe 1 for the y-column
                }
            }

            var destYCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcYCol), srcYCol.GetType(), ColumnKind.X, 0);

            for (int i = 0; i < clusterValuesY.Count; ++i)
            {
                destYCol[i] = clusterValuesY[i]; // leave index 0 for the y-column
            }
            if (clusterStdDevY != null)
            {
                var stdYCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcYCol) + "_StdDev", srcYCol.GetType(), ColumnKind.Err, 0);
                for (int i = 0; i < clusterStdDevY.Count; ++i)
                {
                    stdYCol[i] = clusterStdDevY[i]; // leave index 0 for the y-column
                }
            }

            var srcVColumn = srcColumnsToProcess[0];

            // Create as many columns as there are values in the destXColumn

            for (int i = 0; i < clusterValuesX.Count; ++i)
            {
                if (options.ColumnNaming == ConvertXYVToMatrixOptions.OutputNaming.ColAndIndex || string.IsNullOrEmpty(options.ColumnNameFormatString))
                {
                    destTable.DataColumns.EnsureExistence("Col" + i.ToString(), srcVColumn.GetType(), ColumnKind.V, 0);
                }
                else
                {
                    destTable.DataColumns.EnsureExistence(string.Format(options.ColumnNameFormatString, clusterValuesX[i], i), srcVColumn.GetType(), ColumnKind.V, 0);
                }
            }

            var dict = new Dictionary <(int iX, int iY), (AltaxoVariant sum, int count)>();

            for (int i = 0; i < srcVColumn.Count; ++i)
            {
                var iX = xOffset + clusterIndicesX[i];
                var iY = clusterIndicesY[i];

                if (destTable[iX].IsElementEmpty(iY))
                {
                    destTable[iX][iY] = srcVColumn[i];
                }
                else
                {
                    switch (options.ValueAveraging)
                    {
                    case ConvertXYVToMatrixOptions.OutputAveraging.NoneIgnoreUseLastValue:
                        destTable[iX][iY] = srcVColumn[i];
                        break;

                    case ConvertXYVToMatrixOptions.OutputAveraging.NoneThrowException:
                        throw new Exception(string.Format("Multiple data present for X={0}, Y={1}, and average options has been set to throw an exception in this case!", srcXCol[i], srcYCol[i]));

                    case ConvertXYVToMatrixOptions.OutputAveraging.AverageLinear:
                    {
                        if (!dict.ContainsKey((iX, iY)))
                        {
                            dict.Add((iX, iY), (srcVColumn[i], 1)); // if not found in the dictionary, then add the first value that was already in the table
                        }
                        var(sum, count) = dict[(iX, iY)];
		/// <summary>
		/// Expand the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable.
		/// The provided destination table is cleared from all data and property values before.
		/// </summary>
		/// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param>
		/// <param name="options">The settings for expanding.</param>
		/// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param>
		/// <returns>Null if the method finishes successfully, or an error information.</returns>
		public static string ExpandCyclingVariableColumn(DataTableMultipleColumnProxy inputData, ExpandCyclingVariableColumnOptions options, DataTable destTable)
		{
			var srcTable = inputData.DataTable;

			try
			{
				ExpandCyclingVariableColumnDataAndOptions.EnsureCoherence(inputData, true);
			}
			catch (Exception ex)
			{
				return ex.Message;
			}

			destTable.DataColumns.RemoveColumnsAll();
			destTable.PropCols.RemoveColumnsAll();

			DataColumn srcCycCol = inputData.GetDataColumnOrNull(ExpandCyclingVariableColumnDataAndOptions.ColumnWithCyclingVariableIdentifier);
			var repeatRanges = DecomposeIntoRepeatUnits(srcCycCol);

			// check if there is at least one averaged column
			var columnsToAverageOverRepeatPeriod = inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsToAverageIdentifier);
			if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn && columnsToAverageOverRepeatPeriod.Count == 0)
				throw new ArgumentException("In order to let the first averaged column being the x-column, a column to average is needed, but the options didn't provide such column!");

			// get the other columns to process

			var srcColumnsToProcess = new List<DataColumn>(inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsParticipatingIdentifier));
			// subtract cyclic variable column and average columns
			srcColumnsToProcess.Remove(srcCycCol);
			foreach (var col in columnsToAverageOverRepeatPeriod)
				srcColumnsToProcess.Remove(col);

			// --- Create and calculate the averaged columns, for now only temporarily ---
			var propColsTemp = AverageColumns(srcTable, columnsToAverageOverRepeatPeriod, options, repeatRanges);

			// --- avgValueOrder designates the ordering of the first averaged column and therefore of the sorting of the ranges and of the first averaged column
			int[] avgValueOrder = Sorting.CreateIdentityIndices(repeatRanges.Count);
			// --- prepare the sorting of columns by first averaged column ---
			var rangeOrderSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationColumnSorting : options.DestinationRowSorting;
			if (propColsTemp.Length > 0 && rangeOrderSorting != ExpandCyclingVariableColumnOptions.OutputSorting.None)
			{
				avgValueOrder = Sorting.HeapSortVirtually(propColsTemp[0], avgValueOrder);
				if (rangeOrderSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
					Sorting.ReverseArray(avgValueOrder);
			}

			// prepare the sorting of the cycling values
			var cycValueSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationRowSorting : options.DestinationColumnSorting;
			// create a dictionary with the cycling values (unique) and the corresponding ordering index
			var cycValueOrder = GetUniqueValues(srcCycCol, cycValueSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending);

			if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable)
			{
				int[] propColsIdx = CreatePropColsForAveragedColumns(srcTable, columnsToAverageOverRepeatPeriod, options, destTable);

				// --- Fill the x column, take the row sorting into account ---
				var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcCycCol));
				foreach (var entry in cycValueOrder)
					destXCol[entry.Value] = entry.Key;

				if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn)
				{
					// foreach sourceColumnToProcess create as many destination columns as there are cycling ranges available
					foreach (var srcCol in srcColumnsToProcess)
					{
						int nCreatedCol = -1;
						var destColumnsToSort = new AscendingIntegerCollection();
						foreach (int rangeIndex in avgValueOrder)
						{
							var range = repeatRanges[rangeIndex];
							++nCreatedCol;
							var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
							var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
							destColumnsToSort.Add(nDestCol);
							foreach (var nSrcRow in range)
							{
								int nDestRow = cycValueOrder[srcCycCol[nSrcRow]];
								destCol[nDestRow] = srcCol[nSrcRow];
							}
							// fill also property columns
							for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++)
							{
								destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex];
							}
						}
					} // repeat for each source colum to process
				}
				else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns)
				{
					int nCreatedCol = -1; // running number of processed range for column creation (Naming)
					foreach (int rangeIndex in avgValueOrder)
					{
						var range = repeatRanges[rangeIndex];
						++nCreatedCol;
						foreach (var srcCol in srcColumnsToProcess)
						{
							var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
							var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
							foreach (var nSrcRow in range)
							{
								int nDestRow = cycValueOrder[srcCycCol[nSrcRow]];
								destCol[nDestRow] = srcCol[nSrcRow];
							}
							// fill also property columns
							for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++)
							{
								destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex];
							}
						}
					}
				}
				else
				{
					throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
				}
			}
			else if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn)
			{
				// now the first x column contains the values of the averaged column
				// the rest of the data columns is repeated as many times as there are members in each repeat range
				DataColumn srcXCol = columnsToAverageOverRepeatPeriod[0];
				var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcXCol));

				// Fill with destination X
				for (int nDestRow = 0; nDestRow < repeatRanges.Count; nDestRow++)
					destXCol[nDestRow] = propColsTemp[0][avgValueOrder[nDestRow]];

				// the only property column that is now usefull is that with the repeated values
				var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol));

				if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn)
				{
					foreach (var srcCol in srcColumnsToProcess)
					{
						int nCurrNumber = -1;

						IEnumerable<AltaxoVariant> cycValues = cycValueOrder.Keys;
						if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
							cycValues = cycValueOrder.Keys.Reverse();

						foreach (var cycValue in cycValues)
						{
							++nCurrNumber;
							var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
							var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
							int nDestRow = -1;

							foreach (int rangeIndex in avgValueOrder)
							{
								var range = repeatRanges[rangeIndex];
								++nDestRow;
								int nSrcRow = FindSrcXRow(srcCycCol, cycValue, range);
								if (nSrcRow >= 0)
									destCol[nDestRow] = srcCol[nSrcRow];
							}
							// fill also property column
							destPropCol[nDestCol] = cycValue;
						}
					}
				}
				else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns)
				{
					IEnumerable<AltaxoVariant> positionsKeys = cycValueOrder.Keys;
					if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
						positionsKeys = cycValueOrder.Keys.Reverse();

					int nCurrNumber = -1;
					foreach (var xVal in positionsKeys)
					{
						++nCurrNumber;
						foreach (var srcCol in srcColumnsToProcess)
						{
							var destCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
							var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
							int nDestRow = -1;
							foreach (int rangeIndex in avgValueOrder)
							{
								var range = repeatRanges[rangeIndex];
								++nDestRow;
								int nSrcRow = FindSrcXRow(srcCycCol, xVal, range);
								if (nSrcRow >= 0)
									destCol[nDestRow] = srcCol[nSrcRow];
							}
							// fill also property column
							destPropCol[nDestCol] = xVal;
						}
					}
				}
				else
				{
					throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
				}
			}

			return null;
		}
Example #8
0
        /// <summary>
        /// Expand the source columns according to the provided options. The source table and the settings are provided in the <paramref name="options"/> variable.
        /// The provided destination table is cleared from all data and property values before.
        /// </summary>
        /// <param name="inputData">The data containing the source table, the participating columns and the column with the cycling variable.</param>
        /// <param name="options">The settings for expanding.</param>
        /// <param name="destTable">The destination table. Any data will be removed before filling with the new data.</param>
        /// <returns>Null if the method finishes successfully, or an error information.</returns>
        public static string ExpandCyclingVariableColumn(DataTableMultipleColumnProxy inputData, ExpandCyclingVariableColumnOptions options, DataTable destTable)
        {
            var srcTable = inputData.DataTable;

            try
            {
                ExpandCyclingVariableColumnDataAndOptions.EnsureCoherence(inputData, true);
            }
            catch (Exception ex)
            {
                return(ex.Message);
            }

            destTable.DataColumns.RemoveColumnsAll();
            destTable.PropCols.RemoveColumnsAll();

            DataColumn srcCycCol    = inputData.GetDataColumnOrNull(ExpandCyclingVariableColumnDataAndOptions.ColumnWithCyclingVariableIdentifier);
            var        repeatRanges = DecomposeIntoRepeatUnits(srcCycCol);

            // check if there is at least one averaged column
            var columnsToAverageOverRepeatPeriod = inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsToAverageIdentifier);

            if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn && columnsToAverageOverRepeatPeriod.Count == 0)
            {
                throw new ArgumentException("In order to let the first averaged column being the x-column, a column to average is needed, but the options didn't provide such column!");
            }

            // get the other columns to process

            var srcColumnsToProcess = new List <DataColumn>(inputData.GetDataColumns(ExpandCyclingVariableColumnDataAndOptions.ColumnsParticipatingIdentifier));

            // subtract cyclic variable column and average columns
            srcColumnsToProcess.Remove(srcCycCol);
            foreach (var col in columnsToAverageOverRepeatPeriod)
            {
                srcColumnsToProcess.Remove(col);
            }

            // --- Create and calculate the averaged columns, for now only temporarily ---
            var propColsTemp = AverageColumns(srcTable, columnsToAverageOverRepeatPeriod, options, repeatRanges);

            // --- avgValueOrder designates the ordering of the first averaged column and therefore of the sorting of the ranges and of the first averaged column
            int[] avgValueOrder = Sorting.CreateIdentityIndices(repeatRanges.Count);
            // --- prepare the sorting of columns by first averaged column ---
            var rangeOrderSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationColumnSorting : options.DestinationRowSorting;

            if (propColsTemp.Length > 0 && rangeOrderSorting != ExpandCyclingVariableColumnOptions.OutputSorting.None)
            {
                avgValueOrder = Sorting.HeapSortVirtually(propColsTemp[0], avgValueOrder);
                if (rangeOrderSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
                {
                    Sorting.ReverseArray(avgValueOrder);
                }
            }

            // prepare the sorting of the cycling values
            var cycValueSorting = options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable ? options.DestinationRowSorting : options.DestinationColumnSorting;
            // create a dictionary with the cycling values (unique) and the corresponding ordering index
            var cycValueOrder = GetUniqueValues(srcCycCol, cycValueSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending);

            if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.CyclingVariable)
            {
                int[] propColsIdx = CreatePropColsForAveragedColumns(srcTable, columnsToAverageOverRepeatPeriod, options, destTable);

                // --- Fill the x column, take the row sorting into account ---
                var destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcCycCol));
                foreach (var entry in cycValueOrder)
                {
                    destXCol[entry.Value] = entry.Key;
                }

                if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn)
                {
                    // foreach sourceColumnToProcess create as many destination columns as there are cycling ranges available
                    foreach (var srcCol in srcColumnsToProcess)
                    {
                        int nCreatedCol       = -1;
                        var destColumnsToSort = new AscendingIntegerCollection();
                        foreach (int rangeIndex in avgValueOrder)
                        {
                            var range = repeatRanges[rangeIndex];
                            ++nCreatedCol;
                            var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
                            var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                            destColumnsToSort.Add(nDestCol);
                            foreach (var nSrcRow in range)
                            {
                                int nDestRow = cycValueOrder[srcCycCol[nSrcRow]];
                                destCol[nDestRow] = srcCol[nSrcRow];
                            }
                            // fill also property columns
                            for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++)
                            {
                                destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex];
                            }
                        }
                    } // repeat for each source colum to process
                }
                else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns)
                {
                    int nCreatedCol = -1; // running number of processed range for column creation (Naming)
                    foreach (int rangeIndex in avgValueOrder)
                    {
                        var range = repeatRanges[rangeIndex];
                        ++nCreatedCol;
                        foreach (var srcCol in srcColumnsToProcess)
                        {
                            var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCreatedCol.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
                            var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                            foreach (var nSrcRow in range)
                            {
                                int nDestRow = cycValueOrder[srcCycCol[nSrcRow]];
                                destCol[nDestRow] = srcCol[nSrcRow];
                            }
                            // fill also property columns
                            for (int nPropCol = 0; nPropCol < propColsTemp.Length; nPropCol++)
                            {
                                destTable.PropCols[propColsIdx[nPropCol]][nDestCol] = propColsTemp[nPropCol][rangeIndex];
                            }
                        }
                    }
                }
                else
                {
                    throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
                }
            }
            else if (options.DestinationX == ExpandCyclingVariableColumnOptions.DestinationXColumn.FirstAveragedColumn)
            {
                // now the first x column contains the values of the averaged column
                // the rest of the data columns is repeated as many times as there are members in each repeat range
                DataColumn srcXCol  = columnsToAverageOverRepeatPeriod[0];
                var        destXCol = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcXCol), srcXCol.GetType(), ColumnKind.X, srcTable.DataColumns.GetColumnGroup(srcXCol));

                // Fill with destination X
                for (int nDestRow = 0; nDestRow < repeatRanges.Count; nDestRow++)
                {
                    destXCol[nDestRow] = propColsTemp[0][avgValueOrder[nDestRow]];
                }

                // the only property column that is now usefull is that with the repeated values
                var destPropCol = destTable.PropCols.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCycCol), srcCycCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCycCol), srcTable.DataColumns.GetColumnGroup(srcCycCol));

                if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupOneColumn)
                {
                    foreach (var srcCol in srcColumnsToProcess)
                    {
                        int nCurrNumber = -1;

                        IEnumerable <AltaxoVariant> cycValues = cycValueOrder.Keys;
                        if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
                        {
                            cycValues = cycValueOrder.Keys.Reverse();
                        }

                        foreach (var cycValue in cycValues)
                        {
                            ++nCurrNumber;
                            var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
                            var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                            int nDestRow = -1;

                            foreach (int rangeIndex in avgValueOrder)
                            {
                                var range = repeatRanges[rangeIndex];
                                ++nDestRow;
                                int nSrcRow = FindSrcXRow(srcCycCol, cycValue, range);
                                if (nSrcRow >= 0)
                                {
                                    destCol[nDestRow] = srcCol[nSrcRow];
                                }
                            }
                            // fill also property column
                            destPropCol[nDestCol] = cycValue;
                        }
                    }
                }
                else if (options.DestinationOutput == ExpandCyclingVariableColumnOptions.OutputFormat.GroupAllColumns)
                {
                    IEnumerable <AltaxoVariant> positionsKeys = cycValueOrder.Keys;
                    if (options.DestinationColumnSorting == ExpandCyclingVariableColumnOptions.OutputSorting.Descending)
                    {
                        positionsKeys = cycValueOrder.Keys.Reverse();
                    }

                    int nCurrNumber = -1;
                    foreach (var xVal in positionsKeys)
                    {
                        ++nCurrNumber;
                        foreach (var srcCol in srcColumnsToProcess)
                        {
                            var destCol  = destTable.DataColumns.EnsureExistence(srcTable.DataColumns.GetColumnName(srcCol) + "." + nCurrNumber.ToString(), srcCol.GetType(), srcTable.DataColumns.GetColumnKind(srcCol), srcTable.DataColumns.GetColumnGroup(srcCol));
                            var nDestCol = destTable.DataColumns.GetColumnNumber(destCol);
                            int nDestRow = -1;
                            foreach (int rangeIndex in avgValueOrder)
                            {
                                var range = repeatRanges[rangeIndex];
                                ++nDestRow;
                                int nSrcRow = FindSrcXRow(srcCycCol, xVal, range);
                                if (nSrcRow >= 0)
                                {
                                    destCol[nDestRow] = srcCol[nSrcRow];
                                }
                            }
                            // fill also property column
                            destPropCol[nDestCol] = xVal;
                        }
                    }
                }
                else
                {
                    throw new NotImplementedException("The option for destination output is unknown: " + options.DestinationOutput.ToString());
                }
            }

            return(null);
        }