public override /**/ double quality()
        {
            return(SpecializedOps_CDRM.qualityTriangular(R));
        }

        /**
         * Solves for X using the QR decomposition.
         *
         * @param B A matrix that is n by m.  Not modified.
         * @param X An n by m matrix where the solution is written to.  Modified.
         */
        //@Override
        public override void solve(CMatrixRMaj B, CMatrixRMaj X)
        {
            if (X.numRows != numCols)
            {
                throw new ArgumentException("Unexpected dimensions for X: X rows = " + X.numRows + " expected = " +
                                            numCols);
            }
            else if (B.numRows != numRows || B.numCols != X.numCols)
            {
                throw new ArgumentException("Unexpected dimensions for B");
            }

            int BnumCols = B.numCols;

            // solve each column one by one
            for (int colB = 0; colB < BnumCols; colB++)
            {
                // make a copy of this column in the vector
                for (int i = 0; i < numRows; i++)
                {
                    int indexB = (i * BnumCols + colB) * 2;
                    a.data[i * 2]     = B.data[indexB];
                    a.data[i * 2 + 1] = B.data[indexB + 1];
                }

                // Solve Qa=b
                // a = Q'b
                // a = Q_{n-1}...Q_2*Q_1*b
                //
                // Q_n*b = (I-gamma*u*u^T)*b = b - u*(gamma*U^T*b)
                for (int n = 0; n < numCols; n++)
                {
                    float[] u = QR[n];

                    float realVV = u[n * 2];
                    float imagVV = u[n * 2 + 1];

                    u[n * 2]     = 1;
                    u[n * 2 + 1] = 0;

                    QrHelperFunctions_CDRM.rank1UpdateMultR(a, u, 0, gammas[n], 0, n, numRows, temp.data);

                    u[n * 2]     = realVV;
                    u[n * 2 + 1] = imagVV;
                }

                // solve for Rx = b using the standard upper triangular solver
                TriangularSolver_CDRM.solveU(R.data, a.data, numCols);

                // save the results
                for (int i = 0; i < numCols; i++)
                {
                    int indexB = (i * BnumCols + colB) * 2;
                    X.data[indexB]     = a.data[i * 2];
                    X.data[indexB + 1] = a.data[i * 2 + 1];
                }
            }
        }
        /**
         * Internal function for computing the decomposition.
         */
        private bool _decompose()
        {
            float[] h = QH.data;

            for (int k = 0; k < N - 2; k++)
            {
                // k = column
                u[k * 2]     = 0;
                u[k * 2 + 1] = 0;
                float max = QrHelperFunctions_CDRM.extractColumnAndMax(QH, k + 1, N, k, u, 0);

                if (max > 0)
                {
                    // -------- set up the reflector Q_k

                    float gamma = QrHelperFunctions_CDRM.computeTauGammaAndDivide(k + 1, N, u, max, tau);
                    gammas[k] = gamma;

                    // divide u by u_0
                    float real_u_0 = u[(k + 1) * 2] + tau.real;
                    float imag_u_0 = u[(k + 1) * 2 + 1] + tau.imaginary;
                    QrHelperFunctions_CDRM.divideElements(k + 2, N, u, 0, real_u_0, imag_u_0);

                    // write the reflector into the lower left column of the matrix
                    for (int i = k + 2; i < N; i++)
                    {
                        h[(i * N + k) * 2]     = u[i * 2];
                        h[(i * N + k) * 2 + 1] = u[i * 2 + 1];
                    }

                    u[(k + 1) * 2]     = 1;
                    u[(k + 1) * 2 + 1] = 0;

                    // ---------- multiply on the left by Q_k
                    QrHelperFunctions_CDRM.rank1UpdateMultR(QH, u, 0, gamma, k + 1, k + 1, N, b);

                    // ---------- multiply on the right by Q_k
                    QrHelperFunctions_CDRM.rank1UpdateMultL(QH, u, 0, gamma, 0, k + 1, N);

                    // since the first element in the householder vector is known to be 1
                    // store the full upper hessenberg
                    h[((k + 1) * N + k) * 2]     = -tau.real * max;
                    h[((k + 1) * N + k) * 2 + 1] = -tau.imaginary * max;
                }
                else
                {
                    gammas[k] = 0;
                }
            }

            return(true);
        }
        /**
         * An orthogonal matrix that has the following property: H = Q<sup>T</sup>AQ
         *
         * @param Q If not null then the results will be stored here.  Otherwise a new matrix will be created.
         * @return The extracted Q matrix.
         */
        public CMatrixRMaj getQ(CMatrixRMaj Q)
        {
            Q = UtilDecompositons_CDRM.checkIdentity(Q, N, N);

            //Arrays.fill(u,0,N*2,0);
            Array.Clear(u, 0, N * 2);
            for (int j = N - 2; j >= 0; j--)
            {
                QrHelperFunctions_CDRM.extractHouseholderColumn(QH, j + 1, N, j, u, 0);
                QrHelperFunctions_CDRM.rank1UpdateMultR(Q, u, 0, gammas[j], j + 1, j + 1, N, b);
            }

            return(Q);
        }
        /**
         * Computes and performs the similar a transform for submatrix k.
         */
        private void similarTransform(int k)
        {
            float[] t = QT.data;

            // find the largest value in this column
            // this is used to normalize the column and mitigate overflow/underflow
            float max = QrHelperFunctions_CDRM.computeRowMax(QT, k, k + 1, N);

            if (max > 0)
            {
                float gamma = QrHelperFunctions_CDRM.computeTauGammaAndDivide(k * N + k + 1, k * N + N, t, max, tau);
                gammas[k] = gamma;

                // divide u by u_0
                float real_u_0 = t[(k * N + k + 1) * 2] + tau.real;
                float imag_u_0 = t[(k * N + k + 1) * 2 + 1] + tau.imaginary;
                QrHelperFunctions_CDRM.divideElements(k + 2, N, t, k * N, real_u_0, imag_u_0);

                // A column is zeroed first.  However a row is being used to store because it reduces
                // cache misses.  Need to compute the conjugate to have the correct householder operation
                for (int i = k + 2; i < N; i++)
                {
                    t[(k * N + i) * 2 + 1] = -t[(k * N + i) * 2 + 1];
                }

                t[(k * N + k + 1) * 2]     = 1.0f;
                t[(k * N + k + 1) * 2 + 1] = 0;

                // ---------- Specialized householder that takes advantage of the symmetry
//            QrHelperFunctions_CDRM.rank1UpdateMultR(QT,QT.data,k*N,gamma,k+1,k+1,N,w);
//            QrHelperFunctions_CDRM.rank1UpdateMultL(QT,QT.data,k*N,gamma,k+1,k+1,N);

                householderSymmetric(k, gamma);

                // since the first element in the householder vector is known to be 1
                // store the full upper hessenberg
                t[(k * N + k + 1) * 2]     = -tau.real * max;
                t[(k * N + k + 1) * 2 + 1] = -tau.imaginary * max;
            }
            else
            {
                gammas[k] = 0;
            }
        }
        /**
         * An orthogonal matrix that has the following property: T = Q<sup>H</sup>AQ
         *
         * @param Q If not null then the results will be stored here.  Otherwise a new matrix will be created.
         * @return The extracted Q matrix.
         */
        //@Override
        public CMatrixRMaj getQ(CMatrixRMaj Q, bool transposed)
        {
            Q = UtilDecompositons_CDRM.checkIdentity(Q, N, N);
            Array.Clear(w, 0, N * 2);

            if (transposed)
            {
                for (int j = N - 2; j >= 0; j--)
                {
                    QrHelperFunctions_CDRM.extractHouseholderRow(QT, j, j + 1, N, w, 0);
                    QrHelperFunctions_CDRM.rank1UpdateMultL(Q, w, 0, gammas[j], j + 1, j + 1, N);
                }
            }
            else
            {
                for (int j = N - 2; j >= 0; j--)
                {
                    QrHelperFunctions_CDRM.extractHouseholderRow(QT, j, j + 1, N, w, 0);
                    QrHelperFunctions_CDRM.rank1UpdateMultR(Q, w, 0, gammas[j], j + 1, j + 1, N, b);
                }
            }

            return(Q);
        }