/// <summary>
        /// Performs the operation: <paramref name="result"/> = generalized_inverse(A) * <paramref name="vector"/>.
        /// The resulting vector overwrites <paramref name="result"/>.
        /// </summary>
        /// <param name="vector">The vector that will be multiplied. Its <see cref="IIndexable1D.Length"/> must be equal to
        /// <see cref="IIndexable2D.NumRows"/> of the original matrix A.
        /// </param>
        /// <param name="result">
        /// Output vector that will be overwritten with the solution of the linear system. Its <see cref="IIndexable1D.Length"/>
        /// must be equal to <see cref="IIndexable2D.NumColumns"/> of the original matrix A.
        /// </param>
        /// <exception cref="NonMatchingDimensionsException">
        /// Thrown if <paramref name="vector"/> or <paramref name="result"/> violate the described constraints.
        /// </exception>
        public void MultiplyGeneralizedInverseMatrixTimesVector(Vector vector, Vector result)
        {
            Preconditions.CheckSystemSolutionDimensions(Order, vector.Length);
            Preconditions.CheckMultiplicationDimensions(Order, result.Length);

            // A^+ * b = [ Aii^-1 * bi; 0], where i are the independent rows/columns
            // TODO: Is this correct?
            CholeskySkyline.SubstituteForward(Order, values, diagOffsets, vector.RawData, result.RawData);
            CholeskySkyline.SubstituteBack(Order, values, diagOffsets, result.RawData);
            foreach (int row in DependentColumns)
            {
                result[row] = 0.0;
            }
        }
        internal static (List <int> dependentColumns, List <double[]> nullSpaceBasis) FactorizeInternal(int order, double[] values,
                                                                                                        int[] diagOffsets, double pivotTolerance)
        {
            var dependentColumns = new List <int>();
            var nullSpaceBasis   = new List <double[]>();

            // Process column j
            for (int j = 0; j < order; ++j)
            {
                int offsetAjj = diagOffsets[j];

                // The number of non-zero entries in column j, above the diagonal and excluding it
                int heightColJ = diagOffsets[j + 1] - offsetAjj - 1; //TODO: reuse the diagOffset form previous iteration.

                // The row index above which col j has only zeros
                int topColJ = j - heightColJ;

                // Update each A[i,j] by traversing the column j from the top until the diagonal.
                // The top now is the min row with a non-zero entry instead of 0.
                for (int i = topColJ; i < j; ++i)
                {
                    int offsetAii = diagOffsets[i];         // TODO: increment/decrement the offset from previous iteration
                    int offsetAij = offsetAjj + j - i;      // TODO: increment/decrement the offset from previous iteration

                    // The number of non-zero entries in column i, above the diagonal and excluding it
                    int heightColI = diagOffsets[i + 1] - offsetAii - 1; //TODO: reuse the diagOffset form previous iteration.

                    // The row index above which col j has only zeros
                    int topColI = i - heightColI;

                    // The row index above which either col j or col i has only zeros: max(topColJ, topColI)
                    int topCommon = (topColI > topColJ) ? topColI : topColJ;

                    // Dot product of the parts of columns j, i (i < j) between: [the common top non-zero row, row i)
                    // for (int k = max(topRowOfColJ, topRowOfColI; k < i; ++k) dotColsIJ += A[k,i] * A[k,j]
                    int    numDotEntries = i - topCommon;
                    double dotColsIJ     = 0.0;
                    for (int t = 1; t <= numDotEntries; ++t) //TODO: BLAS dot
                    {
                        dotColsIJ += values[offsetAii + t] * values[offsetAij + t];
                    }

                    // A[i,j] = (A[i,j] - dotIJ) / A[i,i]
                    values[offsetAij] = (values[offsetAij] - dotColsIJ) / values[offsetAii];
                }

                // Update the diagonal term
                // Dot product with itself of the part of column j between: [the top non-zero row, row j).
                // for (int k = topRowOfColJ; k < j; ++k) dotColsJJ += A[k,j]^2
                double dotColsJJ = 0.0;
                double valueAkj;
                for (int t = 1; t <= heightColJ; ++t) //TODO: BLAS dot. //TODO: the managed version would be better off using the offsetAkj as a loop variable
                {
                    valueAkj   = values[offsetAjj + t];
                    dotColsJJ += valueAkj * valueAkj;
                }

                // if A[j,j] = sqrt(A[j,j]-dotColsJJ), but if the subroot is <= 0, then the matrix is not positive definite
                double subroot = values[offsetAjj] - dotColsJJ;
                if (subroot > pivotTolerance)
                {
                    values[offsetAjj] = Math.Sqrt(subroot); // positive definite
                }
                else if (subroot < -pivotTolerance)         // not positive semidefinite
                {
                    throw new IndefiniteMatrixException($"The leading minor of order {j} (and therefore the matrix itself)"
                                                        + " is negative, and the factorization could not be completed.");
                }
                else // positive semidefinite with rank deficiency
                {
                    // Set the dependent column to identity and partially calculate a new basis vector of the null space.
                    dependentColumns.Add(j);
                    var basisVector = new double[order];
                    nullSpaceBasis.Add(basisVector);

                    // The diagonal entries of the skyline column and the null space basis vector are set to 1.
                    basisVector[j]    = 1.0;
                    values[offsetAjj] = 1.0;

                    // Superdiagonal entries are set to 0 after copying them to the nullspace (and negating them).
                    for (int t = 1; t <= heightColJ; ++t) //TODO: indexing could be written more efficiently
                    {
                        basisVector[j - t]    = -values[offsetAjj + t];
                        values[offsetAjj + t] = 0;
                    }

                    // Subdiagonal entries are also set to 0, but they are stored in subsequent columns i of the skyline format.
                    for (int i = j + 1; i < order; ++i)
                    {
                        int offsetAij = diagOffsets[i] + i - j; // TODO: reuse the indexing from the previous iteration
                        if (offsetAij < diagOffsets[i + 1])
                        {
                            values[offsetAij] = 0;                                 //TODO: originally this was <= ? Which is the correct one?
                        }
                    }
                }
            }

            // The independent columns have been factorized successfully. Apply back substitution to finish the calculation
            // of each vector in the null space basis.
            foreach (var basisVector in nullSpaceBasis)
            {
                CholeskySkyline.SubstituteBack(order, values, diagOffsets, basisVector);
            }
            return(dependentColumns, nullSpaceBasis);
        }