private static int MinThree(RowArray2D <double> x, int row, int col)
        {
            var min    = x[row, col];
            var minIdx = col;

            if (col - 1 >= 0 && x[row, col - 1] < min)
            {
                minIdx = col - 1;
                min    = x[row, col - 1];
            }
            if (col + 1 < x.Cols && x[row, col + 1] < min)
            {
                minIdx = col + 1;
            }
            return(minIdx);
        }
        /// <summary>
        /// computes the aggregation from the bottom up
        /// </summary>
        /// <param name="data">return the aggregated cost. The column with the lowest value in the first row is the starting point of the path</param>
        /// <returns></returns>
        private static RowArray2D <double> ComputeAggregation(RowArray2D <double> data)
        {
            // get rows and columns
            var rows = data.Rows; var cols = data.Cols;
            // crate output
            var x = new RowArray2D <double>(rows, cols);

            // copy the lowest row, as the cost in the lowest row is simply the cost of the original data
            for (int col = 0; col < cols; ++col)
            {
                x[rows - 1, col] = data[rows - 1, col];
            }
            // now process the rows above
            for (int row = rows - 2; row >= 0; --row)
            {
                // simply look in the three possible cells in the row below for the lowest value - that is what the cost will be
                for (int col = 0; col < cols; ++col)
                {
                    var minIdex = MinThree(x, row, col);
                    x[row, col] = data[row, col] + x[row + 1, minIdex];
                }
            }
            return(x);
        }
        /// <summary>
        /// computes the path
        /// </summary>
        /// <param name="data">input data</param>
        /// <returns>a tuple of the aggregate matrix and a list of the cells of the cheapest path</returns>
        public static (RowArray2D <double> aggregated, List <CellIndex> path) ComputePath(RowArray2D <double> data)
        {
            var x    = ComputeAggregation(data);
            var path = new List <CellIndex>();
            // find the minimum in the first row
            int minIdx = Enumerable.Range(0, x.Cols).Aggregate((a, b) => (x[0, a] < x[0, b]) ? a : b);

            // add to as first element in the path
            path.Add(new CellIndex(0, minIdx));
            // for the other rows
            for (int row = 1; row < data.Rows; ++row)
            {
                // find the minimum
                var newMinIdx = MinThree(x, row, minIdx);
                path.Add(new CellIndex(row, newMinIdx));
                minIdx = newMinIdx;
            }
            return(x, path);
        }